From 763068b0a1da642e70571010accdf9109aa11f7a Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 27 Mar 2026 04:32:36 +0000 Subject: [PATCH] chore: finish ax v0.8.0 cleanup Co-Authored-By: Virgil --- CONVENTION_DRIFT_REPORT.md | 12 +++---- cmd/ansible/ansible.go | 9 +++-- cmd/ansible/core_primitives.go | 44 ++++++++++++++++++++++++ core_primitives.go | 61 ++++++++++++++++++++++++++++++++++ executor.go | 7 ++-- go.mod | 3 +- go.sum | 8 ++--- modules.go | 11 +++--- parser.go | 15 ++++----- ssh.go | 11 +++--- test_primitives_test.go | 6 ++-- types.go | 2 +- 12 files changed, 141 insertions(+), 48 deletions(-) diff --git a/CONVENTION_DRIFT_REPORT.md b/CONVENTION_DRIFT_REPORT.md index dcf0eaf..0b1ce58 100644 --- a/CONVENTION_DRIFT_REPORT.md +++ b/CONVENTION_DRIFT_REPORT.md @@ -16,8 +16,8 @@ go tool cover -func=/tmp/ansible.cover No direct `stdlib`-to-`core.*` wrapper drift was found in the Go implementation. The remaining drift is stale migration residue around the `core.*` move: -- `go.mod:15`, `go.sum:7`, `go.sum:8` - Legacy `forge.lthn.ai/core/go-log` references still remain in the dependency graph. +- Resolved on the current branch: + Legacy `forge.lthn.ai/core/go-log` references were removed by replacing `dappco.re/go/core/io` usage with the filesystem primitives in `dappco.re/go/core`. - `CLAUDE.md:37`, `docs/development.md:169` Repository guidance still refers to `core/cli`, while the current command registration lives on the `dappco.re/go/core` API at `cmd/ansible/cmd.go:8`. - `CLAUDE.md:66`, `docs/development.md:86` @@ -25,12 +25,8 @@ No direct `stdlib`-to-`core.*` wrapper drift was found in the Go implementation. ## UK English -- `executor.go:248` - Comment uses US spelling: `Initialize host results`. -- `parser.go:321` - Comment uses US spelling: `NormalizeModule normalizes a module name to its canonical form.` -- `types.go:110` - Comment uses US spelling: `LoopControl controls loop behavior.` +- Resolved on the current branch: + Comment spelling now uses `Initialise`, `normalises`, and `behaviour` in the affected code paths. ## Missing Tests diff --git a/cmd/ansible/ansible.go b/cmd/ansible/ansible.go index 4795e79..48f5deb 100644 --- a/cmd/ansible/ansible.go +++ b/cmd/ansible/ansible.go @@ -6,7 +6,6 @@ import ( "dappco.re/go/core" "dappco.re/go/core/ansible" - coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" ) @@ -35,7 +34,7 @@ func runAnsible(opts core.Options) core.Result { playbookPath = absPath(playbookPath) } - if !coreio.Local.Exists(playbookPath) { + if !localFS.Exists(playbookPath) { return core.Result{Value: coreerr.E("runAnsible", sprintf("playbook not found: %s", playbookPath), nil)} } @@ -72,14 +71,14 @@ func runAnsible(opts core.Options) core.Result { invPath = absPath(invPath) } - if !coreio.Local.Exists(invPath) { + if !localFS.Exists(invPath) { return core.Result{Value: coreerr.E("runAnsible", sprintf("inventory not found: %s", invPath), nil)} } - if coreio.Local.IsDir(invPath) { + if localFS.IsDir(invPath) { for _, name := range []string{"inventory.yml", "hosts.yml", "inventory.yaml", "hosts.yaml"} { p := joinPath(invPath, name) - if coreio.Local.Exists(p) { + if localFS.Exists(p) { invPath = p break } diff --git a/cmd/ansible/core_primitives.go b/cmd/ansible/core_primitives.go index 683146d..0ee9f8e 100644 --- a/cmd/ansible/core_primitives.go +++ b/cmd/ansible/core_primitives.go @@ -1,12 +1,56 @@ package anscmd import ( + "io/fs" "unicode" "unicode/utf8" "dappco.re/go/core" ) +type localFileSystem struct { + fs *core.Fs +} + +var localFS = localFileSystem{fs: (&core.Fs{}).NewUnrestricted()} + +func (l localFileSystem) Exists(path string) bool { + return l.fs.Exists(path) +} + +func (l localFileSystem) IsDir(path string) bool { + return l.fs.IsDir(path) +} + +func (l localFileSystem) Write(path, content string) error { + result := l.fs.Write(path, content) + if !result.OK { + return resultError("write "+path, result) + } + return nil +} + +func (l localFileSystem) WriteMode(path, content string, mode fs.FileMode) error { + result := l.fs.WriteMode(path, content, mode) + if !result.OK { + return resultError("write "+path, result) + } + return nil +} + +func resultError(op string, result core.Result) error { + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + if result.Value == nil { + return core.E(op, "operation failed", nil) + } + return core.E(op, core.Sprint(result.Value), nil) +} + func absPath(path string) string { if path == "" { return core.Env("DIR_CWD") diff --git a/core_primitives.go b/core_primitives.go index c8ba482..6f16517 100644 --- a/core_primitives.go +++ b/core_primitives.go @@ -1,6 +1,7 @@ package ansible import ( + "io/fs" "unicode" "unicode/utf8" @@ -13,6 +14,66 @@ type stringBuffer interface { String() string } +type corexLocalFS struct { + fs *core.Fs +} + +var localFS = corexLocalFS{fs: (&core.Fs{}).NewUnrestricted()} + +func (l corexLocalFS) Read(path string) (string, error) { + result := l.fs.Read(path) + if !result.OK { + return "", corexResultError("read "+path, result) + } + content, _ := result.Value.(string) + return content, nil +} + +func (l corexLocalFS) Write(path, content string) error { + result := l.fs.Write(path, content) + if !result.OK { + return corexResultError("write "+path, result) + } + return nil +} + +func (l corexLocalFS) WriteMode(path, content string, mode fs.FileMode) error { + result := l.fs.WriteMode(path, content, mode) + if !result.OK { + return corexResultError("write "+path, result) + } + return nil +} + +func (l corexLocalFS) EnsureDir(path string) error { + result := l.fs.EnsureDir(path) + if !result.OK { + return corexResultError("ensure dir "+path, result) + } + return nil +} + +func (l corexLocalFS) Exists(path string) bool { + return l.fs.Exists(path) +} + +func (l corexLocalFS) IsDir(path string) bool { + return l.fs.IsDir(path) +} + +func corexResultError(op string, result core.Result) error { + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + if result.Value == nil { + return core.E(op, "operation failed", nil) + } + return core.E(op, core.Sprint(result.Value), nil) +} + func dirSep() string { ds := core.Env("DS") if ds == "" { diff --git a/executor.go b/executor.go index 21010a7..29ae00f 100644 --- a/executor.go +++ b/executor.go @@ -8,7 +8,6 @@ import ( "text/template" "time" - coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" ) @@ -266,7 +265,7 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, task *Task, p e.OnTaskStart(host, task) } - // Initialize host results + // Initialise host results if e.results[host] == nil { e.results[host] = make(map[string]*TaskResult) } @@ -903,7 +902,7 @@ func (e *Executor) handleLookup(expr string) string { case "env": return env(arg) case "file": - if data, err := coreio.Local.Read(arg); err == nil { + if data, err := localFS.Read(arg); err == nil { return data } } @@ -1000,7 +999,7 @@ func (e *Executor) Close() { // // content, err := exec.TemplateFile("/workspace/templates/app.conf.j2", "web1", &Task{}) func (e *Executor) TemplateFile(src, host string, task *Task) (string, error) { - content, err := coreio.Local.Read(src) + content, err := localFS.Read(src) if err != nil { return "", err } diff --git a/go.mod b/go.mod index b0ea219..18bccde 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.26.0 require ( dappco.re/go/core v0.8.0-alpha.1 - dappco.re/go/core/io v0.2.0 dappco.re/go/core/log v0.1.0 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.49.0 @@ -12,8 +11,8 @@ require ( ) require ( - forge.lthn.ai/core/go-log v0.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect golang.org/x/sys v0.42.0 // indirect ) diff --git a/go.sum b/go.sum index 47503e9..54961a4 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,8 @@ dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= -dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4= -dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E= dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= -forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= -forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -16,14 +13,17 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/modules.go b/modules.go index 0c4e189..c239b8c 100644 --- a/modules.go +++ b/modules.go @@ -6,7 +6,6 @@ import ( "io/fs" "strconv" - coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" ) @@ -254,7 +253,7 @@ func (e *Executor) moduleScript(ctx context.Context, client *SSHClient, args map } // Read local script - data, err := coreio.Local.Read(script) + data, err := localFS.Read(script) if err != nil { return nil, coreerr.E("Executor.moduleScript", "read script", err) } @@ -285,7 +284,7 @@ func (e *Executor) moduleCopy(ctx context.Context, client *SSHClient, args map[s var err error if src := getStringArg(args, "src", ""); src != "" { - content, err = coreio.Local.Read(src) + content, err = localFS.Read(src) if err != nil { return nil, coreerr.E("Executor.moduleCopy", "read src", err) } @@ -509,11 +508,11 @@ func (e *Executor) moduleFetch(ctx context.Context, client *SSHClient, args map[ } // Create dest directory - if err := coreio.Local.EnsureDir(pathDir(dest)); err != nil { + if err := localFS.EnsureDir(pathDir(dest)); err != nil { return nil, err } - if err := coreio.Local.Write(dest, string(content)); err != nil { + if err := localFS.Write(dest, string(content)); err != nil { return nil, err } @@ -1051,7 +1050,7 @@ func (e *Executor) moduleUnarchive(ctx context.Context, client *SSHClient, args var cmd string if !remote { // Upload local file first - data, err := coreio.Local.Read(src) + data, err := localFS.Read(src) if err != nil { return nil, coreerr.E("Executor.moduleUnarchive", "read src", err) } diff --git a/parser.go b/parser.go index aa4bc18..b3ee17c 100644 --- a/parser.go +++ b/parser.go @@ -5,7 +5,6 @@ import ( "maps" "slices" - coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) @@ -38,7 +37,7 @@ func NewParser(basePath string) *Parser { // // plays, err := parser.ParsePlaybook("/workspace/playbooks/site.yml") func (p *Parser) ParsePlaybook(path string) ([]Play, error) { - data, err := coreio.Local.Read(path) + data, err := localFS.Read(path) if err != nil { return nil, coreerr.E("Parser.ParsePlaybook", "read playbook", err) } @@ -83,7 +82,7 @@ func (p *Parser) ParsePlaybookIter(path string) (iter.Seq[Play], error) { // // inv, err := parser.ParseInventory("/workspace/inventory.yml") func (p *Parser) ParseInventory(path string) (*Inventory, error) { - data, err := coreio.Local.Read(path) + data, err := localFS.Read(path) if err != nil { return nil, coreerr.E("Parser.ParseInventory", "read inventory", err) } @@ -102,7 +101,7 @@ func (p *Parser) ParseInventory(path string) (*Inventory, error) { // // tasks, err := parser.ParseTasks("/workspace/roles/web/tasks/main.yml") func (p *Parser) ParseTasks(path string) ([]Task, error) { - data, err := coreio.Local.Read(path) + data, err := localFS.Read(path) if err != nil { return nil, coreerr.E("Parser.ParseTasks", "read tasks", err) } @@ -168,7 +167,7 @@ func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error) { for _, sp := range searchPaths { // Clean the path to resolve .. segments sp = cleanPath(sp) - if coreio.Local.Exists(sp) { + if localFS.Exists(sp) { tasksPath = sp break } @@ -180,7 +179,7 @@ func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error) { // Load role defaults defaultsPath := joinPath(pathDir(pathDir(tasksPath)), "defaults", "main.yml") - if data, err := coreio.Local.Read(defaultsPath); err == nil { + if data, err := localFS.Read(defaultsPath); err == nil { var defaults map[string]any if yaml.Unmarshal([]byte(data), &defaults) == nil { for k, v := range defaults { @@ -193,7 +192,7 @@ func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error) { // Load role vars varsPath := joinPath(pathDir(pathDir(tasksPath)), "vars", "main.yml") - if data, err := coreio.Local.Read(varsPath); err == nil { + if data, err := localFS.Read(varsPath); err == nil { var roleVars map[string]any if yaml.Unmarshal([]byte(data), &roleVars) == nil { for k, v := range roleVars { @@ -352,7 +351,7 @@ func isModule(key string) bool { return contains(key, ".") } -// NormalizeModule normalizes a module name to its canonical form. +// NormalizeModule normalises a module name to its canonical form. // // Example: // diff --git a/ssh.go b/ssh.go index 26ef65a..3611fd6 100644 --- a/ssh.go +++ b/ssh.go @@ -9,7 +9,6 @@ import ( "sync" "time" - coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" @@ -104,7 +103,7 @@ func (c *SSHClient) Connect(ctx context.Context) error { keyPath = joinPath(env("DIR_HOME"), keyPath[1:]) } - if key, err := coreio.Local.Read(keyPath); err == nil { + if key, err := localFS.Read(keyPath); err == nil { if signer, err := ssh.ParsePrivateKey([]byte(key)); err == nil { authMethods = append(authMethods, ssh.PublicKeys(signer)) } @@ -119,7 +118,7 @@ func (c *SSHClient) Connect(ctx context.Context) error { joinPath(home, ".ssh", "id_rsa"), } for _, keyPath := range defaultKeys { - if key, err := coreio.Local.Read(keyPath); err == nil { + if key, err := localFS.Read(keyPath); err == nil { if signer, err := ssh.ParsePrivateKey([]byte(key)); err == nil { authMethods = append(authMethods, ssh.PublicKeys(signer)) break @@ -154,11 +153,11 @@ func (c *SSHClient) Connect(ctx context.Context) error { knownHostsPath := joinPath(home, ".ssh", "known_hosts") // Ensure known_hosts file exists - if !coreio.Local.Exists(knownHostsPath) { - if err := coreio.Local.EnsureDir(pathDir(knownHostsPath)); err != nil { + if !localFS.Exists(knownHostsPath) { + if err := localFS.EnsureDir(pathDir(knownHostsPath)); err != nil { return coreerr.E("ssh.Connect", "failed to create .ssh dir", err) } - if err := coreio.Local.Write(knownHostsPath, ""); err != nil { + if err := localFS.Write(knownHostsPath, ""); err != nil { return coreerr.E("ssh.Connect", "failed to create known_hosts file", err) } } diff --git a/test_primitives_test.go b/test_primitives_test.go index ad11b98..366eba8 100644 --- a/test_primitives_test.go +++ b/test_primitives_test.go @@ -2,12 +2,10 @@ package ansible import ( "io/fs" - - coreio "dappco.re/go/core/io" ) func readTestFile(path string) ([]byte, error) { - content, err := coreio.Local.Read(path) + content, err := localFS.Read(path) if err != nil { return nil, err } @@ -15,7 +13,7 @@ func readTestFile(path string) ([]byte, error) { } func writeTestFile(path string, content []byte, mode fs.FileMode) error { - return coreio.Local.WriteMode(path, string(content), mode) + return localFS.WriteMode(path, string(content), mode) } func joinStrings(parts []string, sep string) string { diff --git a/types.go b/types.go index 1907a78..f5322fa 100644 --- a/types.go +++ b/types.go @@ -128,7 +128,7 @@ type Task struct { raw map[string]any } -// LoopControl controls loop behavior. +// LoopControl controls loop behaviour. // // Example: //