fix(go-cli): confine agent runtime paths

This commit is contained in:
ZacharyZhang-NY committed 2026-07-19 16:53:34 -04:00
1 parent 1d83323d16
commit 78cb95e288
10 files changed
+588 -78

No files matched your search

+2
View File
@@ -60,6 +60,8 @@ func main() {
The SDK includes a CLI for installing audit hooks into AI coding agents. The SDK includes a CLI for installing audit hooks into AI coding agents.
Agent IDs map to one physical directory directly under `~/.elydora`; portable filename rules and physical directory/config checks apply before writes or recursive removal. Ambiguous uninstall discovery requires an explicit agent ID.
```bash ```bash
go install github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora@latest go install github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora@latest
+18 -57
View File
@@ -1,7 +1,6 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"os" "os"
@@ -12,7 +11,7 @@ import (
"github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora/plugins" "github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora/plugins"
) )
const version = "1.1.0" const version = "1.2.1"
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
@@ -92,16 +91,13 @@ func cmdInstall(args []string) {
} }
// Generate guard script (PreToolUse — freeze enforcement) // Generate guard script (PreToolUse — freeze enforcement)
guardScriptPath, err := guardScriptPathForAgent(*agentID) agentDirectory, err := plugins.PrepareAgentRuntimeDirectory(*agentID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
guardScriptPath := filepath.Join(agentDirectory, "guard.js")
guardScript := plugins.GenerateGuardScript(*agent, *agentID) guardScript := plugins.GenerateGuardScript(*agent, *agentID)
if err := os.MkdirAll(filepath.Dir(guardScriptPath), 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating guard script directory: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile(guardScriptPath, []byte(guardScript), 0755); err != nil { if err := os.WriteFile(guardScriptPath, []byte(guardScript), 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error writing guard script: %v\n", err) fmt.Fprintf(os.Stderr, "Error writing guard script: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -149,13 +145,14 @@ func cmdUninstall(args []string) {
os.Exit(1) os.Exit(1)
} }
// Resolve agentID: if not given, scan ~/.elydora/*/config.json for matching agent_name resolvedAgentID, agentDirectory, agentDirectoryExists, err := resolveAgentRuntimeForUninstall(*agent, *agentID)
resolvedAgentID := *agentID if err != nil {
if resolvedAgentID == "" { fmt.Fprintf(os.Stderr, "Error: %v\n", err)
resolvedAgentID = findAgentIDByName(*agent) os.Exit(1)
if resolvedAgentID == "" {
fmt.Fprintf(os.Stderr, "Warning: could not find agent ID for %q in ~/.elydora/*/config.json\n", *agent)
} }
if resolvedAgentID == "" {
fmt.Fprintf(os.Stderr, "Error: could not find agent ID for %q in ~/.elydora/*/config.json; pass --agent-id explicitly\n", *agent)
os.Exit(1)
} }
if err := plugin.Uninstall(resolvedAgentID); err != nil { if err := plugin.Uninstall(resolvedAgentID); err != nil {
@@ -164,50 +161,14 @@ func cmdUninstall(args []string) {
} }
// Remove the per-agent directory // Remove the per-agent directory
if resolvedAgentID != "" { if agentDirectoryExists {
home, err := os.UserHomeDir() if err := os.RemoveAll(agentDirectory); err != nil {
if err == nil { fmt.Fprintf(os.Stderr, "Error removing agent directory %s: %v\n", agentDirectory, err)
agentDir := filepath.Join(home, ".elydora", resolvedAgentID) os.Exit(1)
if err := os.RemoveAll(agentDir); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to remove agent directory %s: %v\n", agentDir, err)
} else {
fmt.Printf(" Removed agent directory: %s\n", agentDir)
} }
fmt.Printf(" Removed agent directory: %s\n", agentDirectory)
} }
} }
}
// findAgentIDByName scans ~/.elydora/*/config.json for a config whose agent_name
// matches the given name and returns the directory name (agent ID).
func findAgentIDByName(agentName string) string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
elydoraDir := filepath.Join(home, ".elydora")
entries, err := os.ReadDir(elydoraDir)
if err != nil {
return ""
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
configPath := filepath.Join(elydoraDir, entry.Name(), "config.json")
data, err := os.ReadFile(configPath)
if err != nil {
continue
}
var config map[string]interface{}
if err := json.Unmarshal(data, &config); err != nil {
continue
}
if name, _ := config["agent_name"].(string); name == agentName {
return entry.Name()
}
}
return ""
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// status // status
@@ -285,9 +246,9 @@ func sortedAgentNames() []string {
// guardScriptPathForAgent returns the path to ~/.elydora/<agentId>/guard.js. // guardScriptPathForAgent returns the path to ~/.elydora/<agentId>/guard.js.
func guardScriptPathForAgent(agentId string) (string, error) { func guardScriptPathForAgent(agentId string) (string, error) {
home, err := os.UserHomeDir() agentDirectory, err := plugins.ResolveAgentRuntimeDirectory(agentId)
if err != nil { if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err) return "", err
} }
return filepath.Join(home, ".elydora", agentId, "guard.js"), nil return filepath.Join(agentDirectory, "guard.js"), nil
} }
+136
View File
@@ -0,0 +1,136 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func setMainTestHome(t *testing.T) string {
t.Helper()
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
return home
}
func TestGuardScriptPathRejectsUnsafeAgentID(t *testing.T) {
setMainTestHome(t)
_, err := guardScriptPathForAgent("../escape")
if err == nil || !strings.Contains(err.Error(), "invalid agent ID") {
t.Fatalf("guardScriptPathForAgent() error = %v", err)
}
}
func TestFindAgentIDByNameReturnsValidatedDirectoryIdentity(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "agent-1")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
config := []byte(`{"agent_name":"opencode","agent_id":"agent-1"}`)
if err := os.WriteFile(filepath.Join(runtimeDirectory, "config.json"), config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
agentID, err := findAgentIDByName("opencode")
if err != nil {
t.Fatalf("findAgentIDByName() error = %v", err)
}
if agentID != "agent-1" {
t.Fatalf("agent ID = %q, want agent-1", agentID)
}
}
func TestFindAgentIDByNameRejectsConfigDirectoryMismatch(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "stored-directory")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
config := []byte(`{"agent_name":"opencode","agent_id":"different-agent"}`)
if err := os.WriteFile(filepath.Join(runtimeDirectory, "config.json"), config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "crosses its runtime directory") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
func TestFindAgentIDByNameRejectsAmbiguousRuntimes(t *testing.T) {
home := setMainTestHome(t)
for _, agentID := range []string{"agent-1", "agent-2"} {
runtimeDirectory := filepath.Join(home, ".elydora", agentID)
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
config := []byte(`{"agent_name":"opencode","agent_id":"` + agentID + `"}`)
if err := os.WriteFile(filepath.Join(runtimeDirectory, "config.json"), config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "multiple") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
func TestExplicitUninstallRejectsAgentOwnershipMismatch(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "agent-1")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
config := []byte(`{"agent_name":"codex","agent_id":"agent-1"}`)
if err := os.WriteFile(filepath.Join(runtimeDirectory, "config.json"), config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
_, _, _, err := resolveAgentRuntimeForUninstall("opencode", "agent-1")
if err == nil || !strings.Contains(err.Error(), "belongs to") {
t.Fatalf("resolveAgentRuntimeForUninstall() error = %v", err)
}
}
func TestFindAgentIDByNameRejectsLinkedRuntime(t *testing.T) {
home := setMainTestHome(t)
root := filepath.Join(home, ".elydora")
if err := os.MkdirAll(root, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
target := t.TempDir()
link := filepath.Join(root, "agent-1")
if err := os.Symlink(target, link); err != nil {
t.Skipf("directory symbolic links unavailable: %v", err)
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "physical directory") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
func TestFindAgentIDByNameRejectsLinkedConfig(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "agent-1")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
target := filepath.Join(t.TempDir(), "config.json")
config := []byte(`{"agent_name":"opencode","agent_id":"agent-1"}`)
if err := os.WriteFile(target, config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
if err := os.Symlink(target, filepath.Join(runtimeDirectory, "config.json")); err != nil {
t.Skipf("file symbolic links unavailable: %v", err)
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "physical file") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
+5 -4
View File
@@ -153,14 +153,15 @@ func (p *DroidPlugin) Status() (PluginStatus, error) {
} }
func droidAgentDirectory(agentID string) (string, string, error) { func droidAgentDirectory(agentID string) (string, string, error) {
if agentID == "" || agentID == "." || agentID == ".." || filepath.IsAbs(agentID) || filepath.Base(agentID) != agentID {
return "", "", fmt.Errorf("agent_id must be a single non-empty path segment")
}
runtimeRoot, err := droidRuntimeRoot() runtimeRoot, err := droidRuntimeRoot()
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return runtimeRoot, filepath.Join(runtimeRoot, agentID), nil agentDirectory, err := ResolveAgentRuntimeDirectory(agentID)
if err != nil {
return "", "", err
}
return runtimeRoot, agentDirectory, nil
} }
func droidSettingsDocument(settings *droidDocument) *droidDocument { func droidSettingsDocument(settings *droidDocument) *droidDocument {
+3 -7
View File
@@ -12,7 +12,7 @@ import (
// It always exits 0 so it never blocks the host agent. // It always exits 0 so it never blocks the host agent.
func GenerateHookScript(destPath string, config InstallConfig) error { func GenerateHookScript(destPath string, config InstallConfig) error {
dir := filepath.Dir(destPath) dir := filepath.Dir(destPath)
if err := os.MkdirAll(dir, 0755); err != nil { if err := EnsurePrivateDirectory(dir); err != nil {
return fmt.Errorf("create hook script directory: %w", err) return fmt.Errorf("create hook script directory: %w", err)
} }
@@ -32,13 +32,9 @@ func GenerateHookScript(destPath string, config InstallConfig) error {
// ~/.elydora/<agentId>/config.json and ~/.elydora/<agentId>/private.key so that // ~/.elydora/<agentId>/config.json and ~/.elydora/<agentId>/private.key so that
// the generated hook script can read them at runtime without embedding secrets. // the generated hook script can read them at runtime without embedding secrets.
func writeAgentConfig(config InstallConfig) error { func writeAgentConfig(config InstallConfig) error {
home, err := os.UserHomeDir() agentDir, err := PrepareAgentRuntimeDirectory(config.AgentID)
if err != nil { if err != nil {
return fmt.Errorf("resolve home directory: %w", err) return err
}
agentDir := filepath.Join(home, ".elydora", config.AgentID)
if err := os.MkdirAll(agentDir, 0755); err != nil {
return fmt.Errorf("create agent directory: %w", err)
} }
baseURL := config.BaseURL baseURL := config.BaseURL
+6 -6
View File
@@ -55,18 +55,18 @@ func writeJSONFile(path string, data map[string]interface{}) error {
// hookScriptPath returns the default path for the hook script inside ~/.elydora/<agentId>/. // hookScriptPath returns the default path for the hook script inside ~/.elydora/<agentId>/.
func hookScriptPath(agentId string) (string, error) { func hookScriptPath(agentId string) (string, error) {
home, err := os.UserHomeDir() agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
if err != nil { if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err) return "", err
} }
return filepath.Join(home, ".elydora", agentId, "hook.js"), nil return filepath.Join(agentDirectory, "hook.js"), nil
} }
// guardScriptPath returns the default path for the guard script inside ~/.elydora/<agentId>/. // guardScriptPath returns the default path for the guard script inside ~/.elydora/<agentId>/.
func guardScriptPath(agentId string) (string, error) { func guardScriptPath(agentId string) (string, error) {
home, err := os.UserHomeDir() agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
if err != nil { if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err) return "", err
} }
return filepath.Join(home, ".elydora", agentId, "guard.js"), nil return filepath.Join(agentDirectory, "guard.js"), nil
} }
+5 -4
View File
@@ -119,14 +119,15 @@ func (p *QwenPlugin) Status() (PluginStatus, error) {
} }
func qwenAgentDirectory(agentID string) (string, string, error) { func qwenAgentDirectory(agentID string) (string, string, error) {
if agentID == "" || agentID == "." || agentID == ".." || filepath.IsAbs(agentID) || filepath.Base(agentID) != agentID {
return "", "", fmt.Errorf("agent_id must be a single non-empty path segment")
}
runtimeRoot, err := qwenRuntimeRoot() runtimeRoot, err := qwenRuntimeRoot()
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return runtimeRoot, filepath.Join(runtimeRoot, agentID), nil agentDirectory, err := ResolveAgentRuntimeDirectory(agentID)
if err != nil {
return "", "", err
}
return runtimeRoot, agentDirectory, nil
} }
func prepareQwenInstallationChanges( func prepareQwenInstallationChanges(
@@ -0,0 +1,143 @@
package plugins
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
)
var windowsDeviceName = regexp.MustCompile(
`(?i)^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)`,
)
// AgentRuntimeRoot returns the local root for all Elydora agent runtimes.
func AgentRuntimeRoot() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err)
}
root, err := filepath.Abs(filepath.Join(home, ".elydora"))
if err != nil {
return "", fmt.Errorf("resolve Elydora runtime root: %w", err)
}
return root, nil
}
// ResolveAgentRuntimeDirectory confines an agent ID to one portable path segment.
func ResolveAgentRuntimeDirectory(agentID string) (string, error) {
if invalidAgentDirectoryName(agentID) {
return "", fmt.Errorf(
"invalid agent ID for local storage: %q must be a single non-empty path segment with a portable file name",
agentID,
)
}
root, err := AgentRuntimeRoot()
if err != nil {
return "", err
}
candidate := filepath.Join(root, agentID)
relative, err := filepath.Rel(root, candidate)
if err != nil {
return "", fmt.Errorf("resolve agent runtime path for %q: %w", agentID, err)
}
if relative == "." || relative == ".." || filepath.IsAbs(relative) ||
strings.HasPrefix(relative, ".."+string(os.PathSeparator)) ||
strings.ContainsRune(relative, os.PathSeparator) {
return "", fmt.Errorf("agent ID escapes the local storage directory: %q", agentID)
}
if _, err := RequirePhysicalDirectory(root); err != nil {
return "", err
}
if _, err := RequirePhysicalDirectory(candidate); err != nil {
return "", err
}
return candidate, nil
}
func invalidAgentDirectoryName(agentID string) bool {
if agentID == "" || agentID == "." || agentID == ".." ||
strings.ContainsAny(agentID, `<>:"/\|?*`) ||
strings.HasPrefix(agentID, " ") || strings.HasSuffix(agentID, ".") ||
strings.HasSuffix(agentID, " ") ||
windowsDeviceName.MatchString(agentID) {
return true
}
for _, character := range agentID {
if character <= 31 {
return true
}
}
return false
}
// EnsurePrivateDirectory creates an owner-only physical directory.
func EnsurePrivateDirectory(path string) error {
if err := os.MkdirAll(path, 0700); err != nil {
return fmt.Errorf("create private directory %s: %w", path, err)
}
info, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect private directory %s: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return fmt.Errorf("private directory path is not a physical directory: %s", path)
}
if runtime.GOOS != "windows" {
if err := os.Chmod(path, 0700); err != nil {
return fmt.Errorf("restrict private directory %s: %w", path, err)
}
}
return nil
}
// PrepareAgentRuntimeDirectory validates and creates the private runtime path.
func PrepareAgentRuntimeDirectory(agentID string) (string, error) {
root, err := AgentRuntimeRoot()
if err != nil {
return "", err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(agentID)
if err != nil {
return "", err
}
if err := EnsurePrivateDirectory(root); err != nil {
return "", err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return "", err
}
return agentDirectory, nil
}
// RequirePhysicalDirectory reports whether a path is an existing physical directory.
func RequirePhysicalDirectory(path string) (bool, error) {
info, err := os.Lstat(path)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect agent runtime directory %s: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return false, fmt.Errorf("agent runtime path is not a physical directory: %s", path)
}
return true, nil
}
// RequirePhysicalFile reports whether a path is an existing physical regular file.
func RequirePhysicalFile(path string) (bool, error) {
info, err := os.Lstat(path)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect agent runtime file %s: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return false, fmt.Errorf("agent runtime config is not a physical file: %s", path)
}
return true, nil
}
@@ -0,0 +1,111 @@
package plugins
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func setRuntimeTestHome(t *testing.T) string {
t.Helper()
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
return home
}
func TestResolveAgentRuntimeDirectoryRejectsUnsafePortableNames(t *testing.T) {
setRuntimeTestHome(t)
invalid := []string{
"../escape",
`..\escape`,
"C:escape",
"agent.",
"agent ",
" agent",
"CON",
"COM¹.log",
".",
"..",
}
for _, agentID := range invalid {
t.Run(agentID, func(t *testing.T) {
_, err := ResolveAgentRuntimeDirectory(agentID)
if err == nil || !strings.Contains(err.Error(), "invalid agent ID") {
t.Fatalf("ResolveAgentRuntimeDirectory(%q) error = %v", agentID, err)
}
})
}
}
func TestPrepareAgentRuntimeDirectoryCreatesPrivatePhysicalChild(t *testing.T) {
home := setRuntimeTestHome(t)
agentDirectory, err := PrepareAgentRuntimeDirectory("agent-1")
if err != nil {
t.Fatalf("PrepareAgentRuntimeDirectory() error = %v", err)
}
want := filepath.Join(home, ".elydora", "agent-1")
if agentDirectory != want {
t.Fatalf("agent directory = %q, want %q", agentDirectory, want)
}
for _, directory := range []string{filepath.Dir(agentDirectory), agentDirectory} {
info, err := os.Lstat(directory)
if err != nil {
t.Fatalf("Lstat(%q) error = %v", directory, err)
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
t.Fatalf("%q is not a physical directory", directory)
}
if runtime.GOOS != "windows" && info.Mode().Perm() != 0700 {
t.Fatalf("%q mode = %o, want 700", directory, info.Mode().Perm())
}
}
}
func TestPrepareAgentRuntimeDirectoryRejectsSymlinkRoot(t *testing.T) {
home := setRuntimeTestHome(t)
target := t.TempDir()
root := filepath.Join(home, ".elydora")
if err := os.Symlink(target, root); err != nil {
t.Skipf("directory symbolic links unavailable: %v", err)
}
_, err := PrepareAgentRuntimeDirectory("agent-1")
if err == nil || !strings.Contains(err.Error(), "physical directory") {
t.Fatalf("PrepareAgentRuntimeDirectory() error = %v", err)
}
}
func TestResolveAgentRuntimeDirectoryRejectsSymlinkChild(t *testing.T) {
home := setRuntimeTestHome(t)
root := filepath.Join(home, ".elydora")
if err := os.MkdirAll(root, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
target := t.TempDir()
link := filepath.Join(root, "agent-1")
if err := os.Symlink(target, link); err != nil {
t.Skipf("directory symbolic links unavailable: %v", err)
}
_, err := ResolveAgentRuntimeDirectory("agent-1")
if err == nil || !strings.Contains(err.Error(), "physical directory") {
t.Fatalf("ResolveAgentRuntimeDirectory() error = %v", err)
}
}
func TestRequirePhysicalFileRejectsSymlink(t *testing.T) {
setRuntimeTestHome(t)
target := filepath.Join(t.TempDir(), "target.json")
if err := os.WriteFile(target, []byte("{}"), 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
link := filepath.Join(t.TempDir(), "config.json")
if err := os.Symlink(target, link); err != nil {
t.Skipf("file symbolic links unavailable: %v", err)
}
_, err := RequirePhysicalFile(link)
if err == nil || !strings.Contains(err.Error(), "physical file") {
t.Fatalf("RequirePhysicalFile() error = %v", err)
}
}
+159
View File
@@ -0,0 +1,159 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora/plugins"
)
type installedAgentRuntime struct {
agentID string
agentName string
}
type runtimeIdentity struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name"`
}
func readInstalledAgentRuntime(directoryName string) (*installedAgentRuntime, error) {
agentDirectory, err := plugins.ResolveAgentRuntimeDirectory(directoryName)
if err != nil {
return nil, err
}
exists, err := plugins.RequirePhysicalDirectory(agentDirectory)
if err != nil || !exists {
return nil, err
}
configPath := filepath.Join(agentDirectory, "config.json")
configExists, err := plugins.RequirePhysicalFile(configPath)
if err != nil || !configExists {
return nil, err
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("read agent config %s: %w", configPath, err)
}
var identity runtimeIdentity
if err := json.Unmarshal(data, &identity); err != nil {
return nil, fmt.Errorf("parse agent config %s: %w", configPath, err)
}
if identity.AgentID == "" || identity.AgentName == "" {
return nil, fmt.Errorf("agent config %s has an invalid runtime identity", configPath)
}
if identity.AgentID != directoryName {
return nil, fmt.Errorf("agent config %s crosses its runtime directory", configPath)
}
if _, err := plugins.ResolveAgentRuntimeDirectory(identity.AgentID); err != nil {
return nil, fmt.Errorf("validate agent config %s: %w", configPath, err)
}
return &installedAgentRuntime{
agentID: identity.AgentID, agentName: identity.AgentName,
}, nil
}
func discoverInstalledAgentRuntimes() ([]installedAgentRuntime, error) {
runtimeRoot, err := plugins.AgentRuntimeRoot()
if err != nil {
return nil, err
}
exists, err := plugins.RequirePhysicalDirectory(runtimeRoot)
if err != nil || !exists {
return nil, err
}
entries, err := os.ReadDir(runtimeRoot)
if err != nil {
return nil, fmt.Errorf("read Elydora runtime root %s: %w", runtimeRoot, err)
}
runtimes := make([]installedAgentRuntime, 0, len(entries))
for _, entry := range entries {
if entry.Type()&os.ModeSymlink != 0 {
return nil, fmt.Errorf(
"agent runtime path is not a physical directory: %s",
filepath.Join(runtimeRoot, entry.Name()),
)
}
if !entry.IsDir() {
continue
}
runtime, err := readInstalledAgentRuntime(entry.Name())
if err != nil {
return nil, err
}
if runtime != nil {
runtimes = append(runtimes, *runtime)
}
}
return runtimes, nil
}
// findAgentIDByName returns one validated runtime identity for an agent name.
func findAgentIDByName(agentName string) (string, error) {
runtimes, err := discoverInstalledAgentRuntimes()
if err != nil {
return "", err
}
foundAgentID := ""
for _, runtime := range runtimes {
if runtime.agentName != agentName {
continue
}
if foundAgentID != "" {
return "", fmt.Errorf(
"multiple %q agent runtimes found; pass --agent-id explicitly",
agentName,
)
}
foundAgentID = runtime.agentID
}
return foundAgentID, nil
}
func resolveAgentRuntimeForUninstall(
agentName, explicitAgentID string,
) (agentID, agentDirectory string, directoryExists bool, err error) {
runtimeRoot, err := plugins.AgentRuntimeRoot()
if err != nil {
return "", "", false, err
}
runtimeRootExists, err := plugins.RequirePhysicalDirectory(runtimeRoot)
if err != nil {
return "", "", false, err
}
agentID = explicitAgentID
if agentID == "" {
agentID, err = findAgentIDByName(agentName)
if err != nil || agentID == "" {
return agentID, "", false, err
}
}
agentDirectory, err = plugins.ResolveAgentRuntimeDirectory(agentID)
if err != nil {
return "", "", false, err
}
if runtimeRootExists {
directoryExists, err = plugins.RequirePhysicalDirectory(agentDirectory)
if err != nil {
return "", "", false, err
}
}
if directoryExists {
runtime, err := readInstalledAgentRuntime(agentID)
if err != nil {
return "", "", false, err
}
if runtime != nil && runtime.agentName != agentName {
return "", "", false, fmt.Errorf(
"agent runtime %q belongs to %q, not %q",
agentID, runtime.agentName, agentName,
)
}
}
return agentID, agentDirectory, directoryExists, nil
}