[agent/codex:gpt-5.4-mini] AX compliance pass. Fix banned stdlib imports, error handlin... #9
27 changed files with 199 additions and 136 deletions
|
|
@ -230,7 +230,7 @@ func stopContainer(id string) error {
|
|||
// Support partial ID matching
|
||||
fullID, err := resolveContainerID(manager, id)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("stopContainer", "resolve container ID", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.stop.stopping")), fullID[:8])
|
||||
|
|
@ -249,7 +249,7 @@ func resolveContainerID(manager *container.LinuxKitManager, partialID string) (s
|
|||
ctx := context.Background()
|
||||
containers, err := manager.List(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("resolveContainerID", "list containers", err)
|
||||
}
|
||||
|
||||
var matches []*container.Container
|
||||
|
|
@ -298,7 +298,7 @@ func viewLogs(id string, follow bool) error {
|
|||
|
||||
fullID, err := resolveContainerID(manager, id)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("viewLogs", "resolve container ID", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -309,7 +309,10 @@ func viewLogs(id string, follow bool) error {
|
|||
defer func() { _ = reader.Close() }()
|
||||
|
||||
_, err = goio.Copy(os.Stdout, reader)
|
||||
return err
|
||||
if err != nil {
|
||||
return coreerr.E("viewLogs", "copy log output", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addVMExecCommand adds the 'exec' command under vm.
|
||||
|
|
@ -337,9 +340,12 @@ func execInContainer(id string, cmd []string) error {
|
|||
|
||||
fullID, err := resolveContainerID(manager, id)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("execInContainer", "resolve container ID", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
return manager.Exec(ctx, fullID, cmd)
|
||||
if err := manager.Exec(ctx, fullID, cmd); err != nil {
|
||||
return coreerr.E("execInContainer", "run command in container", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func listTemplates() error {
|
|||
func showTemplate(name string) error {
|
||||
content, err := container.GetTemplate(name)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("showTemplate", "get template", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("common.label.template")), repoNameStyle.Render(name))
|
||||
|
|
@ -114,7 +114,7 @@ func showTemplate(name string) error {
|
|||
func showTemplateVars(name string) error {
|
||||
content, err := container.GetTemplate(name)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("showTemplateVars", "get template", err)
|
||||
}
|
||||
|
||||
required, optional := container.ExtractVariables(content)
|
||||
|
|
@ -155,11 +155,15 @@ func RunFromTemplate(templateName string, vars map[string]string, runOpts contai
|
|||
}
|
||||
|
||||
// Create a temporary directory for the build
|
||||
tmpDir, err := os.MkdirTemp("", "core-linuxkit-*")
|
||||
tmpID, err := container.GenerateID()
|
||||
if err != nil {
|
||||
return coreerr.E("RunFromTemplate", i18n.T("common.error.failed", map[string]any{"Action": "create temp directory"}), err)
|
||||
}
|
||||
defer func() { _ = os.RemoveAll(tmpDir) }()
|
||||
tmpDir := filepath.Join(os.TempDir(), "core-linuxkit-"+tmpID)
|
||||
if err := io.Local.EnsureDir(tmpDir); err != nil {
|
||||
return coreerr.E("RunFromTemplate", i18n.T("common.error.failed", map[string]any{"Action": "create temp directory"}), err)
|
||||
}
|
||||
defer func() { _ = io.Local.DeleteAll(tmpDir) }()
|
||||
|
||||
// Write the YAML file
|
||||
yamlPath := filepath.Join(tmpDir, templateName+".yml")
|
||||
|
|
@ -218,7 +222,7 @@ func buildLinuxKitImage(yamlPath, outputPath string) error {
|
|||
// Check if linuxkit is available
|
||||
lkPath, err := lookupLinuxKit()
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("buildLinuxKitImage", "find linuxkit binary", err)
|
||||
}
|
||||
|
||||
// Build the image
|
||||
|
|
@ -231,7 +235,10 @@ func buildLinuxKitImage(yamlPath, outputPath string) error {
|
|||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return coreerr.E("buildLinuxKitImage", "run linuxkit build", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findBuiltImage finds the built image file.
|
||||
|
|
@ -241,7 +248,7 @@ func findBuiltImage(basePath string) string {
|
|||
|
||||
for _, ext := range extensions {
|
||||
path := basePath + ext
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if io.Local.IsFile(path) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
|
@ -250,7 +257,7 @@ func findBuiltImage(basePath string) string {
|
|||
dir := filepath.Dir(basePath)
|
||||
base := filepath.Base(basePath)
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
entries, err := io.Local.List(dir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -283,7 +290,7 @@ func lookupLinuxKit() (string, error) {
|
|||
}
|
||||
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
if io.Local.IsFile(p) {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"encoding/hex"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// Container represents a running LinuxKit container/VM instance.
|
||||
|
|
@ -84,7 +86,7 @@ type Manager interface {
|
|||
func GenerateID() (string, error) {
|
||||
bytes := make([]byte, 4)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("GenerateID", "read random bytes", err)
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func (d *DevOps) Claude(ctx context.Context, projectDir string, opts ClaudeOptio
|
|||
// Auto-boot if not running
|
||||
running, err := d.IsRunning(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Claude", "check running state", err)
|
||||
}
|
||||
if !running {
|
||||
fmt.Println("Dev environment not running, booting...")
|
||||
|
|
@ -123,7 +123,7 @@ func formatAuthList(opts ClaudeOptions) string {
|
|||
func (d *DevOps) CopyGHAuth(ctx context.Context) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.CopyGHAuth", "get home dir", err)
|
||||
}
|
||||
|
||||
ghConfigDir := filepath.Join(home, ".config", "gh")
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClaudeOptions_Default(t *testing.T) {
|
||||
func TestClaudeOptions_Good_Default(t *testing.T) {
|
||||
opts := ClaudeOptions{}
|
||||
assert.False(t, opts.NoAuth)
|
||||
assert.Nil(t, opts.Auth)
|
||||
assert.Empty(t, opts.Model)
|
||||
}
|
||||
|
||||
func TestClaudeOptions_Custom(t *testing.T) {
|
||||
func TestClaudeOptions_Good_Custom(t *testing.T) {
|
||||
opts := ClaudeOptions{
|
||||
NoAuth: true,
|
||||
Auth: []string{"gh", "anthropic"},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"forge.lthn.ai/core/config"
|
||||
)
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ func DefaultConfig() *Config {
|
|||
func ConfigPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("ConfigPath", "get home dir", err)
|
||||
}
|
||||
return filepath.Join(home, ".core", "config.yaml"), nil
|
||||
}
|
||||
|
|
@ -79,11 +80,11 @@ func LoadConfig(m io.Medium) (*Config, error) {
|
|||
// Use centralized config service
|
||||
c, err := config.New(config.WithMedium(m), config.WithPath(configPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadConfig", "create config service", err)
|
||||
}
|
||||
|
||||
if err := c.Get("", cfg); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadConfig", "load config", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
func TestDefaultConfig_Good(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.Equal(t, 1, cfg.Version)
|
||||
assert.Equal(t, "auto", cfg.Images.Source)
|
||||
assert.Equal(t, "host-uk/core-images", cfg.Images.GitHub.Repo)
|
||||
}
|
||||
|
||||
func TestConfigPath(t *testing.T) {
|
||||
func TestConfigPath_Good(t *testing.T) {
|
||||
path, err := ConfigPath()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, path, ".core/config.yaml")
|
||||
|
|
@ -79,7 +79,7 @@ func TestLoadConfig_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestConfig_Struct(t *testing.T) {
|
||||
func TestConfig_Good_Struct(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Version: 2,
|
||||
Images: ImagesConfig{
|
||||
|
|
@ -102,7 +102,7 @@ func TestConfig_Struct(t *testing.T) {
|
|||
assert.Equal(t, "https://cdn.example.com", cfg.Images.CDN.URL)
|
||||
}
|
||||
|
||||
func TestDefaultConfig_Complete(t *testing.T) {
|
||||
func TestDefaultConfig_Good_Complete(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.Equal(t, 1, cfg.Version)
|
||||
assert.Equal(t, "auto", cfg.Images.Source)
|
||||
|
|
@ -205,7 +205,7 @@ images:
|
|||
}
|
||||
}
|
||||
|
||||
func TestImagesConfig_Struct(t *testing.T) {
|
||||
func TestImagesConfig_Good_Struct(t *testing.T) {
|
||||
ic := ImagesConfig{
|
||||
Source: "auto",
|
||||
GitHub: GitHubConfig{Repo: "test/repo"},
|
||||
|
|
@ -214,17 +214,17 @@ func TestImagesConfig_Struct(t *testing.T) {
|
|||
assert.Equal(t, "test/repo", ic.GitHub.Repo)
|
||||
}
|
||||
|
||||
func TestGitHubConfig_Struct(t *testing.T) {
|
||||
func TestGitHubConfig_Good_Struct(t *testing.T) {
|
||||
gc := GitHubConfig{Repo: "owner/repo"}
|
||||
assert.Equal(t, "owner/repo", gc.Repo)
|
||||
}
|
||||
|
||||
func TestRegistryConfig_Struct(t *testing.T) {
|
||||
func TestRegistryConfig_Good_Struct(t *testing.T) {
|
||||
rc := RegistryConfig{Image: "ghcr.io/owner/image:latest"}
|
||||
assert.Equal(t, "ghcr.io/owner/image:latest", rc.Image)
|
||||
}
|
||||
|
||||
func TestCDNConfig_Struct(t *testing.T) {
|
||||
func TestCDNConfig_Good_Struct(t *testing.T) {
|
||||
cc := CDNConfig{URL: "https://cdn.example.com/images"}
|
||||
assert.Equal(t, "https://cdn.example.com/images", cc.URL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func ImagesDir() (string, error) {
|
|||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("ImagesDir", "get home dir", err)
|
||||
}
|
||||
return filepath.Join(home, ".core", "images"), nil
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func ImagesDir() (string, error) {
|
|||
func ImagePath() (string, error) {
|
||||
dir, err := ImagesDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("ImagePath", "get images directory", err)
|
||||
}
|
||||
return filepath.Join(dir, ImageName()), nil
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@ func (d *DevOps) Boot(ctx context.Context, opts BootOptions) error {
|
|||
|
||||
imagePath, err := ImagePath()
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Boot", "get image path", err)
|
||||
}
|
||||
|
||||
// Build run options for LinuxKitManager
|
||||
|
|
@ -149,7 +149,7 @@ func (d *DevOps) Boot(ctx context.Context, opts BootOptions) error {
|
|||
|
||||
_, err = d.container.Run(ctx, imagePath, runOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Boot", "run container", err)
|
||||
}
|
||||
|
||||
// Wait for SSH to be ready and scan host key
|
||||
|
|
@ -175,7 +175,7 @@ func (d *DevOps) Boot(ctx context.Context, opts BootOptions) error {
|
|||
func (d *DevOps) Stop(ctx context.Context) error {
|
||||
c, err := d.findContainer(ctx, "core-dev")
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Stop", "find container", err)
|
||||
}
|
||||
if c == nil {
|
||||
return coreerr.E("DevOps.Stop", "dev environment not found", nil)
|
||||
|
|
@ -187,7 +187,7 @@ func (d *DevOps) Stop(ctx context.Context) error {
|
|||
func (d *DevOps) IsRunning(ctx context.Context) (bool, error) {
|
||||
c, err := d.findContainer(ctx, "core-dev")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, coreerr.E("DevOps.IsRunning", "find container", err)
|
||||
}
|
||||
return c != nil && c.Status == container.StatusRunning, nil
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ func (d *DevOps) IsRunning(ctx context.Context) (bool, error) {
|
|||
func (d *DevOps) findContainer(ctx context.Context, name string) (*container.Container, error) {
|
||||
containers, err := d.container.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("DevOps.findContainer", "list containers", err)
|
||||
}
|
||||
for _, c := range containers {
|
||||
if c.Name == name {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
func TestImageName_Good(t *testing.T) {
|
||||
name := ImageName()
|
||||
assert.Contains(t, name, "core-devops-")
|
||||
assert.Contains(t, name, runtime.GOOS)
|
||||
|
|
@ -23,7 +23,7 @@ func TestImageName(t *testing.T) {
|
|||
assert.True(t, (name[len(name)-6:] == ".qcow2"))
|
||||
}
|
||||
|
||||
func TestImagesDir(t *testing.T) {
|
||||
func TestImagesDir_Good(t *testing.T) {
|
||||
t.Run("default directory", func(t *testing.T) {
|
||||
// Unset env if it exists
|
||||
orig := os.Getenv("CORE_IMAGES_DIR")
|
||||
|
|
@ -45,7 +45,7 @@ func TestImagesDir(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestImagePath(t *testing.T) {
|
||||
func TestImagePath_Good(t *testing.T) {
|
||||
customDir := "/tmp/images"
|
||||
t.Setenv("CORE_IMAGES_DIR", customDir)
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ func TestImagePath(t *testing.T) {
|
|||
assert.Equal(t, expected, path)
|
||||
}
|
||||
|
||||
func TestDefaultBootOptions(t *testing.T) {
|
||||
func TestDefaultBootOptions_Good(t *testing.T) {
|
||||
opts := DefaultBootOptions()
|
||||
assert.Equal(t, 4096, opts.Memory)
|
||||
assert.Equal(t, 2, opts.CPUs)
|
||||
|
|
@ -366,7 +366,7 @@ func TestDevOps_Stop_Bad_NotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "not found")
|
||||
}
|
||||
|
||||
func TestBootOptions_Custom(t *testing.T) {
|
||||
func TestBootOptions_Good_Custom(t *testing.T) {
|
||||
opts := BootOptions{
|
||||
Memory: 8192,
|
||||
CPUs: 4,
|
||||
|
|
@ -379,7 +379,7 @@ func TestBootOptions_Custom(t *testing.T) {
|
|||
assert.True(t, opts.Fresh)
|
||||
}
|
||||
|
||||
func TestDevStatus_Struct(t *testing.T) {
|
||||
func TestDevStatus_Good_Struct(t *testing.T) {
|
||||
status := DevStatus{
|
||||
Installed: true,
|
||||
Running: true,
|
||||
|
|
@ -738,7 +738,7 @@ func TestDevOps_Boot_Good_FreshWithNoExisting(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestImageName_Format(t *testing.T) {
|
||||
func TestImageName_Good_Format(t *testing.T) {
|
||||
name := ImageName()
|
||||
// Check format: core-devops-{os}-{arch}.qcow2
|
||||
assert.Contains(t, name, "core-devops-")
|
||||
|
|
@ -747,7 +747,7 @@ func TestImageName_Format(t *testing.T) {
|
|||
assert.True(t, filepath.Ext(name) == ".qcow2")
|
||||
}
|
||||
|
||||
func TestDevOps_Install_Delegates(t *testing.T) {
|
||||
func TestDevOps_Install_Good_Delegates(t *testing.T) {
|
||||
// This test verifies the Install method delegates to ImageManager
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -765,7 +765,7 @@ func TestDevOps_Install_Delegates(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDevOps_CheckUpdate_Delegates(t *testing.T) {
|
||||
func TestDevOps_CheckUpdate_Good_Delegates(t *testing.T) {
|
||||
// This test verifies the CheckUpdate method delegates to ImageManager
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -815,7 +815,7 @@ func TestDevOps_Boot_Good_Success(t *testing.T) {
|
|||
assert.NoError(t, err) // Mock hypervisor succeeds
|
||||
}
|
||||
|
||||
func TestDevOps_Config(t *testing.T) {
|
||||
func TestDevOps_Good_Config(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
|
|
@ -40,19 +39,19 @@ type ImageInfo struct {
|
|||
func NewImageManager(m io.Medium, cfg *Config) (*ImageManager, error) {
|
||||
imagesDir, err := ImagesDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("NewImageManager", "get images directory", err)
|
||||
}
|
||||
|
||||
// Ensure images directory exists
|
||||
if err := m.EnsureDir(imagesDir); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("NewImageManager", "ensure images directory", err)
|
||||
}
|
||||
|
||||
// Load or create manifest
|
||||
manifestPath := filepath.Join(imagesDir, "manifest.json")
|
||||
manifest, err := loadManifest(m, manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("NewImageManager", "load manifest", err)
|
||||
}
|
||||
|
||||
// Build source list based on config
|
||||
|
|
@ -98,7 +97,7 @@ func (m *ImageManager) IsInstalled() bool {
|
|||
func (m *ImageManager) Install(ctx context.Context, progress func(downloaded, total int64)) error {
|
||||
imagesDir, err := ImagesDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("ImageManager.Install", "get images directory", err)
|
||||
}
|
||||
|
||||
// Find first available source
|
||||
|
|
@ -123,7 +122,7 @@ func (m *ImageManager) Install(ctx context.Context, progress func(downloaded, to
|
|||
|
||||
// Download
|
||||
if err := src.Download(ctx, m.medium, imagesDir, progress); err != nil {
|
||||
return err
|
||||
return coreerr.E("ImageManager.Install", "download image", err)
|
||||
}
|
||||
|
||||
// Update manifest
|
||||
|
|
@ -133,7 +132,10 @@ func (m *ImageManager) Install(ctx context.Context, progress func(downloaded, to
|
|||
Source: src.Name(),
|
||||
}
|
||||
|
||||
return m.manifest.Save()
|
||||
if err := m.manifest.Save(); err != nil {
|
||||
return coreerr.E("ImageManager.Install", "save manifest", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckUpdate checks if an update is available.
|
||||
|
|
@ -158,7 +160,7 @@ func (m *ImageManager) CheckUpdate(ctx context.Context) (current, latest string,
|
|||
|
||||
latest, err = src.LatestVersion(ctx)
|
||||
if err != nil {
|
||||
return current, "", false, err
|
||||
return current, "", false, coreerr.E("ImageManager.CheckUpdate", "get latest version", err)
|
||||
}
|
||||
|
||||
hasUpdate = current != latest
|
||||
|
|
@ -172,16 +174,17 @@ func loadManifest(m io.Medium, path string) (*Manifest, error) {
|
|||
path: path,
|
||||
}
|
||||
|
||||
if !m.Exists(path) {
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
content, err := m.Read(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return manifest, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, coreerr.E("loadManifest", "read manifest", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(content), manifest); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("loadManifest", "unmarshal manifest", err)
|
||||
}
|
||||
manifest.medium = m
|
||||
manifest.path = path
|
||||
|
|
@ -193,7 +196,10 @@ func loadManifest(m io.Medium, path string) (*Manifest, error) {
|
|||
func (m *Manifest) Save() error {
|
||||
data, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("Manifest.Save", "marshal manifest", err)
|
||||
}
|
||||
return m.medium.Write(m.path, string(data))
|
||||
if err := m.medium.Write(m.path, string(data)); err != nil {
|
||||
return coreerr.E("Manifest.Save", "write manifest", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func TestNewImageManager_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestManifest_Save(t *testing.T) {
|
||||
func TestManifest_Save_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "manifest.json")
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ func TestLoadManifest_Good_ExistingData(t *testing.T) {
|
|||
assert.Equal(t, "cdn", m.Images["test.img"].Source)
|
||||
}
|
||||
|
||||
func TestImageInfo_Struct(t *testing.T) {
|
||||
func TestImageInfo_Good_Struct(t *testing.T) {
|
||||
info := ImageInfo{
|
||||
Version: "1.0.0",
|
||||
SHA256: "abc123",
|
||||
|
|
@ -570,7 +570,7 @@ func TestImageManager_CheckUpdate_Good_FirstSourceUnavailable(t *testing.T) {
|
|||
assert.True(t, hasUpdate)
|
||||
}
|
||||
|
||||
func TestManifest_Struct(t *testing.T) {
|
||||
func TestManifest_Good_Struct(t *testing.T) {
|
||||
m := &Manifest{
|
||||
Images: map[string]ImageInfo{
|
||||
"test.img": {Version: "1.0.0"},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type ServeOptions struct {
|
|||
func (d *DevOps) Serve(ctx context.Context, projectDir string, opts ServeOptions) error {
|
||||
running, err := d.IsRunning(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Serve", "check running state", err)
|
||||
}
|
||||
if !running {
|
||||
return coreerr.E("DevOps.Serve", "dev environment not running (run 'core dev boot' first)", nil)
|
||||
|
|
@ -54,7 +54,7 @@ func (d *DevOps) Serve(ctx context.Context, projectDir string, opts ServeOptions
|
|||
func (d *DevOps) mountProject(ctx context.Context, path string) error {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.mountProject", "get absolute path", err)
|
||||
}
|
||||
|
||||
// Use reverse SSHFS mount
|
||||
|
|
@ -68,7 +68,10 @@ func (d *DevOps) mountProject(ctx context.Context, path string) error {
|
|||
"root@localhost",
|
||||
fmt.Sprintf("mkdir -p /app && sshfs -p 10000 %s@localhost:%s /app -o allow_other", os.Getenv("USER"), absPath),
|
||||
)
|
||||
return cmd.Run()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return coreerr.E("DevOps.mountProject", "run sshfs mount", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectServeCommand auto-detects the serve command for a project.
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ func TestDetectServeCommand_Good_Priority(t *testing.T) {
|
|||
assert.Equal(t, "php artisan octane:start --host=0.0.0.0 --port=8000", cmd)
|
||||
}
|
||||
|
||||
func TestServeOptions_Default(t *testing.T) {
|
||||
func TestServeOptions_Good_Default(t *testing.T) {
|
||||
opts := ServeOptions{}
|
||||
assert.Equal(t, 0, opts.Port)
|
||||
assert.Equal(t, "", opts.Path)
|
||||
}
|
||||
|
||||
func TestServeOptions_Custom(t *testing.T) {
|
||||
func TestServeOptions_Good_Custom(t *testing.T) {
|
||||
opts := ServeOptions{
|
||||
Port: 3000,
|
||||
Path: "public",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type ShellOptions struct {
|
|||
func (d *DevOps) Shell(ctx context.Context, opts ShellOptions) error {
|
||||
running, err := d.IsRunning(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Shell", "check running state", err)
|
||||
}
|
||||
if !running {
|
||||
return coreerr.E("DevOps.Shell", "dev environment not running (run 'core dev boot' first)", nil)
|
||||
|
|
@ -52,7 +52,10 @@ func (d *DevOps) sshShell(ctx context.Context, command []string) error {
|
|||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return coreerr.E("DevOps.sshShell", "run ssh command", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialConsole attaches to the QEMU serial console.
|
||||
|
|
@ -60,7 +63,7 @@ func (d *DevOps) serialConsole(ctx context.Context) error {
|
|||
// Find the container to get its console socket
|
||||
c, err := d.findContainer(ctx, "core-dev")
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.serialConsole", "find container", err)
|
||||
}
|
||||
if c == nil {
|
||||
return coreerr.E("DevOps.serialConsole", "console not available: container not found", nil)
|
||||
|
|
@ -72,5 +75,8 @@ func (d *DevOps) serialConsole(ctx context.Context) error {
|
|||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return coreerr.E("DevOps.serialConsole", "run socat", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShellOptions_Default(t *testing.T) {
|
||||
func TestShellOptions_Good_Default(t *testing.T) {
|
||||
opts := ShellOptions{}
|
||||
assert.False(t, opts.Console)
|
||||
assert.Nil(t, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_Console(t *testing.T) {
|
||||
func TestShellOptions_Good_Console(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Console: true,
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ func TestShellOptions_Console(t *testing.T) {
|
|||
assert.Nil(t, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_Command(t *testing.T) {
|
||||
func TestShellOptions_Good_Command(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Command: []string{"ls", "-la"},
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ func TestShellOptions_Command(t *testing.T) {
|
|||
assert.Equal(t, []string{"ls", "-la"}, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_ConsoleWithCommand(t *testing.T) {
|
||||
func TestShellOptions_Good_ConsoleWithCommand(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Console: true,
|
||||
Command: []string{"echo", "hello"},
|
||||
|
|
@ -37,7 +37,7 @@ func TestShellOptions_ConsoleWithCommand(t *testing.T) {
|
|||
assert.Equal(t, []string{"echo", "hello"}, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_EmptyCommand(t *testing.T) {
|
||||
func TestShellOptions_Good_EmptyCommand(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Command: []string{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,15 +44,22 @@ func ensureHostKey(ctx context.Context, port int) error {
|
|||
}
|
||||
|
||||
// Read existing known_hosts to avoid duplicates
|
||||
existingStr, _ := coreio.Local.Read(knownHostsPath)
|
||||
existingStr := ""
|
||||
if coreio.Local.IsFile(knownHostsPath) {
|
||||
existingStr, err = coreio.Local.Read(knownHostsPath)
|
||||
if err != nil {
|
||||
return coreerr.E("ensureHostKey", "read known_hosts", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Append new keys that aren't already there
|
||||
f, err := os.OpenFile(knownHostsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return coreerr.E("ensureHostKey", "open known_hosts", err)
|
||||
var updated strings.Builder
|
||||
updated.WriteString(existingStr)
|
||||
if len(existingStr) > 0 && !strings.HasSuffix(existingStr, "\n") {
|
||||
updated.WriteString("\n")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
wroteLine := false
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
|
@ -60,11 +67,19 @@ func ensureHostKey(ctx context.Context, port int) error {
|
|||
continue
|
||||
}
|
||||
if !strings.Contains(existingStr, line) {
|
||||
if _, err := f.WriteString(line + "\n"); err != nil {
|
||||
return coreerr.E("ensureHostKey", "write known_hosts", err)
|
||||
}
|
||||
updated.WriteString(line)
|
||||
updated.WriteString("\n")
|
||||
wroteLine = true
|
||||
}
|
||||
}
|
||||
|
||||
if !wroteLine {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := coreio.Local.WriteMode(knownHostsPath, updated.String(), 0600); err != nil {
|
||||
return coreerr.E("ensureHostKey", "write known_hosts", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type TestOptions struct {
|
|||
func (d *DevOps) Test(ctx context.Context, projectDir string, opts TestOptions) error {
|
||||
running, err := d.IsRunning(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Test", "check running state", err)
|
||||
}
|
||||
if !running {
|
||||
return coreerr.E("DevOps.Test", "dev environment not running (run 'core dev boot' first)", nil)
|
||||
|
|
@ -49,7 +49,7 @@ func (d *DevOps) Test(ctx context.Context, projectDir string, opts TestOptions)
|
|||
} else if opts.Name != "" {
|
||||
cfg, err := LoadTestConfig(d.medium, projectDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("DevOps.Test", "load test config", err)
|
||||
}
|
||||
for _, c := range cfg.Commands {
|
||||
if c.Name == opts.Name {
|
||||
|
|
@ -116,17 +116,17 @@ func LoadTestConfig(m io.Medium, projectDir string) (*TestConfig, error) {
|
|||
path := filepath.Join(projectDir, ".core", "test.yaml")
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadTestConfig", "get absolute path", err)
|
||||
}
|
||||
|
||||
content, err := m.Read(absPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadTestConfig", "read test config", err)
|
||||
}
|
||||
|
||||
var cfg TestConfig
|
||||
if err := yaml.Unmarshal([]byte(content), &cfg); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadTestConfig", "unmarshal test config", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func TestHasComposerScript_Bad_MissingScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestConfig_Struct(t *testing.T) {
|
||||
func TestTestConfig_Good_Struct(t *testing.T) {
|
||||
cfg := &TestConfig{
|
||||
Version: 2,
|
||||
Command: "my-test",
|
||||
|
|
@ -201,7 +201,7 @@ func TestTestConfig_Struct(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestCommand_Struct(t *testing.T) {
|
||||
func TestTestCommand_Good_Struct(t *testing.T) {
|
||||
cmd := TestCommand{
|
||||
Name: "integration",
|
||||
Run: "go test -tags=integration ./...",
|
||||
|
|
@ -214,7 +214,7 @@ func TestTestCommand_Struct(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestOptions_Struct(t *testing.T) {
|
||||
func TestTestOptions_Good_Struct(t *testing.T) {
|
||||
opts := TestOptions{
|
||||
Name: "unit",
|
||||
Command: []string{"go", "test", "-v"},
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ package container
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
|
|
@ -141,8 +141,7 @@ func (q *QemuHypervisor) BuildCommand(ctx context.Context, image string, opts *H
|
|||
|
||||
// isKVMAvailable checks if KVM is available on the system.
|
||||
func isKVMAvailable() bool {
|
||||
_, err := os.Stat("/dev/kvm")
|
||||
return err == nil
|
||||
return io.Local.Exists("/dev/kvm")
|
||||
}
|
||||
|
||||
// HyperkitHypervisor implements Hypervisor for macOS Hyperkit.
|
||||
|
|
|
|||
31
linuxkit.go
31
linuxkit.go
|
|
@ -35,7 +35,7 @@ func NewLinuxKitManager(m io.Medium) (*LinuxKitManager, error) {
|
|||
|
||||
hypervisor, err := DetectHypervisor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("NewLinuxKitManager", "detect hypervisor", err)
|
||||
}
|
||||
|
||||
return &LinuxKitManager{
|
||||
|
|
@ -119,7 +119,7 @@ func (m *LinuxKitManager) Run(ctx context.Context, image string, opts RunOptions
|
|||
}
|
||||
|
||||
// Create log file
|
||||
logFile, err := os.Create(logPath)
|
||||
logFile, err := io.Local.Create(logPath)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("LinuxKitManager.Run", "failed to create log file", err)
|
||||
}
|
||||
|
|
@ -298,7 +298,7 @@ func (m *LinuxKitManager) Stop(ctx context.Context, id string) error {
|
|||
// List returns all known containers, verifying process state.
|
||||
func (m *LinuxKitManager) List(ctx context.Context) ([]*Container, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LinuxKitManager.List", "check context", err)
|
||||
}
|
||||
containers := m.state.All()
|
||||
|
||||
|
|
@ -330,7 +330,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) (goio.ReadCloser, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LinuxKitManager.Logs", "check context", err)
|
||||
}
|
||||
_, ok := m.state.Get(id)
|
||||
if !ok {
|
||||
|
|
@ -348,11 +348,19 @@ func (m *LinuxKitManager) Logs(ctx context.Context, id string, follow bool) (goi
|
|||
|
||||
if !follow {
|
||||
// Simple case: just open and return the file
|
||||
return m.medium.Open(logPath)
|
||||
file, err := m.medium.Open(logPath)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("LinuxKitManager.Logs", "open log file", err)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Follow mode: create a reader that tails the file
|
||||
return newFollowReader(ctx, m.medium, logPath)
|
||||
reader, err := newFollowReader(ctx, m.medium, logPath)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("LinuxKitManager.Logs", "create follow reader", err)
|
||||
}
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
// followReader implements goio.ReadCloser for following log files.
|
||||
|
|
@ -368,7 +376,7 @@ type followReader struct {
|
|||
func newFollowReader(ctx context.Context, m io.Medium, path string) (*followReader, error) {
|
||||
file, err := m.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("newFollowReader", "open log file", err)
|
||||
}
|
||||
|
||||
// Note: We don't seek here because Medium.Open doesn't guarantee Seekability.
|
||||
|
|
@ -398,7 +406,7 @@ func (f *followReader) Read(p []byte) (int, error) {
|
|||
return n, nil
|
||||
}
|
||||
if err != nil && err != goio.EOF {
|
||||
return 0, err
|
||||
return 0, coreerr.E("followReader.Read", "read log stream", err)
|
||||
}
|
||||
|
||||
// No data available, wait a bit and try again
|
||||
|
|
@ -420,7 +428,7 @@ func (f *followReader) Close() error {
|
|||
// Exec executes a command inside the container via SSH.
|
||||
func (m *LinuxKitManager) Exec(ctx context.Context, id string, cmd []string) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
return coreerr.E("LinuxKitManager.Exec", "check context", err)
|
||||
}
|
||||
container, ok := m.state.Get(id)
|
||||
if !ok {
|
||||
|
|
@ -449,7 +457,10 @@ func (m *LinuxKitManager) Exec(ctx context.Context, id string, cmd []string) err
|
|||
sshCmd.Stdout = os.Stdout
|
||||
sshCmd.Stderr = os.Stderr
|
||||
|
||||
return sshCmd.Run()
|
||||
if err := sshCmd.Run(); err != nil {
|
||||
return coreerr.E("LinuxKitManager.Exec", "run ssh command", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// State returns the manager's state (for testing).
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ func TestLinuxKitManager_Stop_Good_ContextCancelled(t *testing.T) {
|
|||
err = manager.Stop(cancelCtx, container.ID)
|
||||
// Should return context error
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, context.Canceled, err)
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
}
|
||||
|
||||
func TestIsProcessRunning_Good_ExistingProcess(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
goio "io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
|
|
@ -80,7 +79,7 @@ func (s *CDNSource) Download(ctx context.Context, m io.Medium, dest string, prog
|
|||
|
||||
// Create destination file
|
||||
destPath := filepath.Join(dest, s.config.ImageName)
|
||||
f, err := os.Create(destPath)
|
||||
f, err := m.Create(destPath)
|
||||
if err != nil {
|
||||
return coreerr.E("cdn.Download", "create destination file", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,12 +235,12 @@ func TestCDNSource_Download_Bad_HTTPErrorCodes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCDNSource_InterfaceCompliance(t *testing.T) {
|
||||
func TestCDNSource_Good_InterfaceCompliance(t *testing.T) {
|
||||
// Verify CDNSource implements ImageSource
|
||||
var _ ImageSource = (*CDNSource)(nil)
|
||||
}
|
||||
|
||||
func TestCDNSource_Config(t *testing.T) {
|
||||
func TestCDNSource_Good_Config(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
CDNURL: "https://cdn.example.com",
|
||||
ImageName: "my-image.qcow2",
|
||||
|
|
@ -291,7 +291,7 @@ func TestCDNSource_Download_Good_CreatesDestDir(t *testing.T) {
|
|||
assert.True(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestSourceConfig_Struct(t *testing.T) {
|
||||
func TestSourceConfig_Good_Struct(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
RegistryImage: "ghcr.io/owner/image",
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ func TestGitHubSource_Good_Available(t *testing.T) {
|
|||
_ = src.Available()
|
||||
}
|
||||
|
||||
func TestGitHubSource_Name(t *testing.T) {
|
||||
func TestGitHubSource_Good_Name(t *testing.T) {
|
||||
src := NewGitHubSource(SourceConfig{})
|
||||
assert.Equal(t, "github", src.Name())
|
||||
}
|
||||
|
||||
func TestGitHubSource_Config(t *testing.T) {
|
||||
func TestGitHubSource_Good_Config(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
ImageName: "test-image.qcow2",
|
||||
|
|
@ -62,7 +62,7 @@ func TestNewGitHubSource_Good(t *testing.T) {
|
|||
assert.Equal(t, cfg.GitHubRepo, src.config.GitHubRepo)
|
||||
}
|
||||
|
||||
func TestGitHubSource_InterfaceCompliance(t *testing.T) {
|
||||
func TestGitHubSource_Good_InterfaceCompliance(t *testing.T) {
|
||||
// Verify GitHubSource implements ImageSource
|
||||
var _ ImageSource = (*GitHubSource)(nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSourceConfig_Empty(t *testing.T) {
|
||||
func TestSourceConfig_Good_Empty(t *testing.T) {
|
||||
cfg := SourceConfig{}
|
||||
assert.Empty(t, cfg.GitHubRepo)
|
||||
assert.Empty(t, cfg.RegistryImage)
|
||||
|
|
@ -14,7 +14,7 @@ func TestSourceConfig_Empty(t *testing.T) {
|
|||
assert.Empty(t, cfg.ImageName)
|
||||
}
|
||||
|
||||
func TestSourceConfig_Complete(t *testing.T) {
|
||||
func TestSourceConfig_Good_Complete(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
RegistryImage: "ghcr.io/owner/image:v1",
|
||||
|
|
@ -28,7 +28,7 @@ func TestSourceConfig_Complete(t *testing.T) {
|
|||
assert.Equal(t, "my-image-darwin-arm64.qcow2", cfg.ImageName)
|
||||
}
|
||||
|
||||
func TestImageSource_Interface(t *testing.T) {
|
||||
func TestImageSource_Good_Interface(t *testing.T) {
|
||||
// Ensure both sources implement the interface
|
||||
var _ ImageSource = (*GitHubSource)(nil)
|
||||
var _ ImageSource = (*CDNSource)(nil)
|
||||
|
|
|
|||
36
state.go
36
state.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// State manages persistent container state.
|
||||
|
|
@ -22,7 +23,7 @@ type State struct {
|
|||
func DefaultStateDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("DefaultStateDir", "get home dir", err)
|
||||
}
|
||||
return filepath.Join(home, ".core"), nil
|
||||
}
|
||||
|
|
@ -31,7 +32,7 @@ func DefaultStateDir() (string, error) {
|
|||
func DefaultStatePath() (string, error) {
|
||||
dir, err := DefaultStateDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("DefaultStatePath", "get state directory", err)
|
||||
}
|
||||
return filepath.Join(dir, "containers.json"), nil
|
||||
}
|
||||
|
|
@ -40,7 +41,7 @@ func DefaultStatePath() (string, error) {
|
|||
func DefaultLogsDir() (string, error) {
|
||||
dir, err := DefaultStateDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("DefaultLogsDir", "get state directory", err)
|
||||
}
|
||||
return filepath.Join(dir, "logs"), nil
|
||||
}
|
||||
|
|
@ -58,16 +59,17 @@ func NewState(filePath string) *State {
|
|||
func LoadState(filePath string) (*State, error) {
|
||||
state := NewState(filePath)
|
||||
|
||||
if !io.Local.Exists(filePath) {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
dataStr, err := io.Local.Read(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return state, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadState", "read state file", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(dataStr), state); err != nil {
|
||||
return nil, err
|
||||
return nil, coreerr.E("LoadState", "unmarshal state file", err)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
|
|
@ -81,15 +83,18 @@ func (s *State) SaveState() error {
|
|||
// Ensure the directory exists
|
||||
dir := filepath.Dir(s.filePath)
|
||||
if err := io.Local.EnsureDir(dir); err != nil {
|
||||
return err
|
||||
return coreerr.E("State.SaveState", "ensure state directory", err)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("State.SaveState", "marshal state", err)
|
||||
}
|
||||
|
||||
return io.Local.Write(s.filePath, string(data))
|
||||
if err := io.Local.Write(s.filePath, string(data)); err != nil {
|
||||
return coreerr.E("State.SaveState", "write state file", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a container to the state and persists it.
|
||||
|
|
@ -157,7 +162,7 @@ func (s *State) FilePath() string {
|
|||
func LogPath(id string) (string, error) {
|
||||
logsDir, err := DefaultLogsDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("LogPath", "get logs directory", err)
|
||||
}
|
||||
return filepath.Join(logsDir, id+".log"), nil
|
||||
}
|
||||
|
|
@ -166,7 +171,10 @@ func LogPath(id string) (string, error) {
|
|||
func EnsureLogsDir() error {
|
||||
logsDir, err := DefaultLogsDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return coreerr.E("EnsureLogsDir", "get logs directory", err)
|
||||
}
|
||||
return io.Local.EnsureDir(logsDir)
|
||||
if err := io.Local.EnsureDir(logsDir); err != nil {
|
||||
return coreerr.E("EnsureLogsDir", "ensure logs directory", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ func GetTemplate(name string) (string, error) {
|
|||
func ApplyTemplate(name string, vars map[string]string) (string, error) {
|
||||
content, err := GetTemplate(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", coreerr.E("ApplyTemplate", "get template", err)
|
||||
}
|
||||
|
||||
return ApplyVariables(content, vars)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue