refactor(ansible): migrate module path to dappco.re/go/core/ansible
Update go.mod module line and all dependencies from forge.lthn.ai to dappco.re paths (core v0.5.0, log v0.1.0, io v0.2.0). Update all .go import paths. Rewrite cmd/ansible/ for new core.Command API replacing the cobra-based CLI integration. Update documentation references. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
1f2535bcfa
commit
b3eed66230
14 changed files with 247 additions and 257 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,2 +1,4 @@
|
|||
.core/
|
||||
.idea/
|
||||
.vscode/
|
||||
*.log
|
||||
.core/
|
||||
|
|
|
|||
7
CONSUMERS.md
Normal file
7
CONSUMERS.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Consumers of go-ansible
|
||||
|
||||
These modules import `dappco.re/go/core/ansible`:
|
||||
|
||||
- go-infra
|
||||
|
||||
**Breaking change risk: 1 consumers.**
|
||||
|
|
@ -7,88 +7,31 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
ansible "forge.lthn.ai/core/go-ansible"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
"dappco.re/go/core"
|
||||
"dappco.re/go/core/ansible"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ansibleInventory string
|
||||
ansibleLimit string
|
||||
ansibleTags string
|
||||
ansibleSkipTags string
|
||||
ansibleVars []string
|
||||
ansibleVerbose int
|
||||
ansibleCheck bool
|
||||
)
|
||||
|
||||
var ansibleCmd = &cli.Command{
|
||||
Use: "ansible <playbook>",
|
||||
Short: "Run Ansible playbooks natively (no Python required)",
|
||||
Long: `Execute Ansible playbooks using a pure Go implementation.
|
||||
|
||||
This command parses Ansible YAML playbooks and executes them natively,
|
||||
without requiring Python or ansible-playbook to be installed.
|
||||
|
||||
Supported modules:
|
||||
- shell, command, raw, script
|
||||
- copy, template, file, lineinfile, stat, slurp, fetch, get_url
|
||||
- apt, apt_key, apt_repository, package, pip
|
||||
- service, systemd
|
||||
- user, group
|
||||
- uri, wait_for, git, unarchive
|
||||
- debug, fail, assert, set_fact, pause
|
||||
|
||||
Examples:
|
||||
core ansible playbooks/coolify/create.yml -i inventory/
|
||||
core ansible site.yml -l production
|
||||
core ansible deploy.yml -e "version=1.2.3" -e "env=prod"`,
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: runAnsible,
|
||||
// args extracts all positional arguments from Options.
|
||||
func args(opts core.Options) []string {
|
||||
var out []string
|
||||
for _, o := range opts {
|
||||
if o.Key == "_arg" {
|
||||
if s, ok := o.Value.(string); ok {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var ansibleTestCmd = &cli.Command{
|
||||
Use: "test <host>",
|
||||
Short: "Test SSH connectivity to a host",
|
||||
Long: `Test SSH connection and gather facts from a host.
|
||||
|
||||
Examples:
|
||||
core ansible test linux.snider.dev -u claude -p claude
|
||||
core ansible test server.example.com -i ~/.ssh/id_rsa`,
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: runAnsibleTest,
|
||||
func runAnsible(opts core.Options) core.Result {
|
||||
positional := args(opts)
|
||||
if len(positional) < 1 {
|
||||
return core.Result{Value: coreerr.E("runAnsible", "usage: ansible <playbook>", nil)}
|
||||
}
|
||||
|
||||
var (
|
||||
testUser string
|
||||
testPassword string
|
||||
testKeyFile string
|
||||
testPort int
|
||||
)
|
||||
|
||||
func init() {
|
||||
// ansible command flags
|
||||
ansibleCmd.Flags().StringVarP(&ansibleInventory, "inventory", "i", "", "Inventory file or directory")
|
||||
ansibleCmd.Flags().StringVarP(&ansibleLimit, "limit", "l", "", "Limit to specific hosts")
|
||||
ansibleCmd.Flags().StringVarP(&ansibleTags, "tags", "t", "", "Only run plays and tasks tagged with these values")
|
||||
ansibleCmd.Flags().StringVar(&ansibleSkipTags, "skip-tags", "", "Skip plays and tasks tagged with these values")
|
||||
ansibleCmd.Flags().StringArrayVarP(&ansibleVars, "extra-vars", "e", nil, "Set additional variables (key=value)")
|
||||
ansibleCmd.Flags().CountVarP(&ansibleVerbose, "verbose", "v", "Increase verbosity")
|
||||
ansibleCmd.Flags().BoolVar(&ansibleCheck, "check", false, "Don't make any changes (dry run)")
|
||||
|
||||
// test command flags
|
||||
ansibleTestCmd.Flags().StringVarP(&testUser, "user", "u", "root", "SSH user")
|
||||
ansibleTestCmd.Flags().StringVarP(&testPassword, "password", "p", "", "SSH password")
|
||||
ansibleTestCmd.Flags().StringVarP(&testKeyFile, "key", "i", "", "SSH private key file")
|
||||
ansibleTestCmd.Flags().IntVar(&testPort, "port", 22, "SSH port")
|
||||
|
||||
// Add subcommands
|
||||
ansibleCmd.AddCommand(ansibleTestCmd)
|
||||
}
|
||||
|
||||
func runAnsible(cmd *cli.Command, args []string) error {
|
||||
playbookPath := args[0]
|
||||
playbookPath := positional[0]
|
||||
|
||||
// Resolve playbook path
|
||||
if !filepath.IsAbs(playbookPath) {
|
||||
|
|
@ -96,7 +39,7 @@ func runAnsible(cmd *cli.Command, args []string) error {
|
|||
}
|
||||
|
||||
if !coreio.Local.Exists(playbookPath) {
|
||||
return coreerr.E("runAnsible", fmt.Sprintf("playbook not found: %s", playbookPath), nil)
|
||||
return core.Result{Value: coreerr.E("runAnsible", fmt.Sprintf("playbook not found: %s", playbookPath), nil)}
|
||||
}
|
||||
|
||||
// Create executor
|
||||
|
|
@ -105,38 +48,38 @@ func runAnsible(cmd *cli.Command, args []string) error {
|
|||
defer executor.Close()
|
||||
|
||||
// Set options
|
||||
executor.Limit = ansibleLimit
|
||||
executor.CheckMode = ansibleCheck
|
||||
executor.Verbose = ansibleVerbose
|
||||
executor.Limit = opts.String("limit")
|
||||
executor.CheckMode = opts.Bool("check")
|
||||
executor.Verbose = opts.Int("verbose")
|
||||
|
||||
if ansibleTags != "" {
|
||||
executor.Tags = strings.Split(ansibleTags, ",")
|
||||
if tags := opts.String("tags"); tags != "" {
|
||||
executor.Tags = strings.Split(tags, ",")
|
||||
}
|
||||
if ansibleSkipTags != "" {
|
||||
executor.SkipTags = strings.Split(ansibleSkipTags, ",")
|
||||
if skipTags := opts.String("skip-tags"); skipTags != "" {
|
||||
executor.SkipTags = strings.Split(skipTags, ",")
|
||||
}
|
||||
|
||||
// Parse extra vars
|
||||
for _, v := range ansibleVars {
|
||||
if extraVars := opts.String("extra-vars"); extraVars != "" {
|
||||
for _, v := range strings.Split(extraVars, ",") {
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
executor.SetVar(parts[0], parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load inventory
|
||||
if ansibleInventory != "" {
|
||||
invPath := ansibleInventory
|
||||
if invPath := opts.String("inventory"); invPath != "" {
|
||||
if !filepath.IsAbs(invPath) {
|
||||
invPath, _ = filepath.Abs(invPath)
|
||||
}
|
||||
|
||||
if !coreio.Local.Exists(invPath) {
|
||||
return coreerr.E("runAnsible", fmt.Sprintf("inventory not found: %s", invPath), nil)
|
||||
return core.Result{Value: coreerr.E("runAnsible", fmt.Sprintf("inventory not found: %s", invPath), nil)}
|
||||
}
|
||||
|
||||
if coreio.Local.IsDir(invPath) {
|
||||
// Look for inventory.yml or hosts.yml
|
||||
for _, name := range []string{"inventory.yml", "hosts.yml", "inventory.yaml", "hosts.yaml"} {
|
||||
p := filepath.Join(invPath, name)
|
||||
if coreio.Local.Exists(p) {
|
||||
|
|
@ -147,13 +90,13 @@ func runAnsible(cmd *cli.Command, args []string) error {
|
|||
}
|
||||
|
||||
if err := executor.SetInventory(invPath); err != nil {
|
||||
return coreerr.E("runAnsible", "load inventory", err)
|
||||
return core.Result{Value: coreerr.E("runAnsible", "load inventory", err)}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
executor.OnPlayStart = func(play *ansible.Play) {
|
||||
fmt.Printf("\n%s %s\n", cli.TitleStyle.Render("PLAY"), cli.BoldStyle.Render("["+play.Name+"]"))
|
||||
fmt.Printf("\nPLAY [%s]\n", play.Name)
|
||||
fmt.Println(strings.Repeat("*", 70))
|
||||
}
|
||||
|
||||
|
|
@ -162,41 +105,36 @@ func runAnsible(cmd *cli.Command, args []string) error {
|
|||
if taskName == "" {
|
||||
taskName = task.Module
|
||||
}
|
||||
fmt.Printf("\n%s %s\n", cli.TitleStyle.Render("TASK"), cli.BoldStyle.Render("["+taskName+"]"))
|
||||
if ansibleVerbose > 0 {
|
||||
fmt.Printf("%s\n", cli.DimStyle.Render("host: "+host))
|
||||
fmt.Printf("\nTASK [%s]\n", taskName)
|
||||
if executor.Verbose > 0 {
|
||||
fmt.Printf("host: %s\n", host)
|
||||
}
|
||||
}
|
||||
|
||||
executor.OnTaskEnd = func(host string, task *ansible.Task, result *ansible.TaskResult) {
|
||||
status := "ok"
|
||||
style := cli.SuccessStyle
|
||||
|
||||
if result.Failed {
|
||||
status = "failed"
|
||||
style = cli.ErrorStyle
|
||||
} else if result.Skipped {
|
||||
status = "skipping"
|
||||
style = cli.DimStyle
|
||||
} else if result.Changed {
|
||||
status = "changed"
|
||||
style = cli.WarningStyle
|
||||
}
|
||||
|
||||
fmt.Printf("%s: [%s]", style.Render(status), host)
|
||||
if result.Msg != "" && ansibleVerbose > 0 {
|
||||
fmt.Printf("%s: [%s]", status, host)
|
||||
if result.Msg != "" && executor.Verbose > 0 {
|
||||
fmt.Printf(" => %s", result.Msg)
|
||||
}
|
||||
if result.Duration > 0 && ansibleVerbose > 1 {
|
||||
if result.Duration > 0 && executor.Verbose > 1 {
|
||||
fmt.Printf(" (%s)", result.Duration.Round(time.Millisecond))
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if result.Failed && result.Stderr != "" {
|
||||
fmt.Printf("%s\n", cli.ErrorStyle.Render(result.Stderr))
|
||||
fmt.Printf("%s\n", result.Stderr)
|
||||
}
|
||||
|
||||
if ansibleVerbose > 1 {
|
||||
if executor.Verbose > 1 {
|
||||
if result.Stdout != "" {
|
||||
fmt.Printf("stdout: %s\n", strings.TrimSpace(result.Stdout))
|
||||
}
|
||||
|
|
@ -211,36 +149,38 @@ func runAnsible(cmd *cli.Command, args []string) error {
|
|||
ctx := context.Background()
|
||||
start := time.Now()
|
||||
|
||||
fmt.Printf("%s Running playbook: %s\n", cli.BoldStyle.Render("▶"), playbookPath)
|
||||
fmt.Printf("Running playbook: %s\n", playbookPath)
|
||||
|
||||
if err := executor.Run(ctx, playbookPath); err != nil {
|
||||
return coreerr.E("runAnsible", "playbook failed", err)
|
||||
return core.Result{Value: coreerr.E("runAnsible", "playbook failed", err)}
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s Playbook completed in %s\n",
|
||||
cli.SuccessStyle.Render("✓"),
|
||||
time.Since(start).Round(time.Millisecond))
|
||||
fmt.Printf("\nPlaybook completed in %s\n", time.Since(start).Round(time.Millisecond))
|
||||
|
||||
return nil
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func runAnsibleTest(cmd *cli.Command, args []string) error {
|
||||
host := args[0]
|
||||
func runAnsibleTest(opts core.Options) core.Result {
|
||||
positional := args(opts)
|
||||
if len(positional) < 1 {
|
||||
return core.Result{Value: coreerr.E("runAnsibleTest", "usage: ansible test <host>", nil)}
|
||||
}
|
||||
host := positional[0]
|
||||
|
||||
fmt.Printf("Testing SSH connection to %s...\n", cli.BoldStyle.Render(host))
|
||||
fmt.Printf("Testing SSH connection to %s...\n", host)
|
||||
|
||||
cfg := ansible.SSHConfig{
|
||||
Host: host,
|
||||
Port: testPort,
|
||||
User: testUser,
|
||||
Password: testPassword,
|
||||
KeyFile: testKeyFile,
|
||||
Port: opts.Int("port"),
|
||||
User: opts.String("user"),
|
||||
Password: opts.String("password"),
|
||||
KeyFile: opts.String("key"),
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
client, err := ansible.NewSSHClient(cfg)
|
||||
if err != nil {
|
||||
return coreerr.E("runAnsibleTest", "create client", err)
|
||||
return core.Result{Value: coreerr.E("runAnsibleTest", "create client", err)}
|
||||
}
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
|
|
@ -250,58 +190,50 @@ func runAnsibleTest(cmd *cli.Command, args []string) error {
|
|||
// Test connection
|
||||
start := time.Now()
|
||||
if err := client.Connect(ctx); err != nil {
|
||||
return coreerr.E("runAnsibleTest", "connect failed", err)
|
||||
return core.Result{Value: coreerr.E("runAnsibleTest", "connect failed", err)}
|
||||
}
|
||||
connectTime := time.Since(start)
|
||||
|
||||
fmt.Printf("%s Connected in %s\n", cli.SuccessStyle.Render("✓"), connectTime.Round(time.Millisecond))
|
||||
fmt.Printf("Connected in %s\n", connectTime.Round(time.Millisecond))
|
||||
|
||||
// Gather facts
|
||||
fmt.Println("\nGathering facts...")
|
||||
|
||||
// Hostname
|
||||
stdout, _, _, _ := client.Run(ctx, "hostname -f 2>/dev/null || hostname")
|
||||
fmt.Printf(" Hostname: %s\n", cli.BoldStyle.Render(strings.TrimSpace(stdout)))
|
||||
fmt.Printf(" Hostname: %s\n", strings.TrimSpace(stdout))
|
||||
|
||||
// OS
|
||||
stdout, _, _, _ = client.Run(ctx, "cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2")
|
||||
if stdout != "" {
|
||||
fmt.Printf(" OS: %s\n", strings.TrimSpace(stdout))
|
||||
}
|
||||
|
||||
// Kernel
|
||||
stdout, _, _, _ = client.Run(ctx, "uname -r")
|
||||
fmt.Printf(" Kernel: %s\n", strings.TrimSpace(stdout))
|
||||
|
||||
// Architecture
|
||||
stdout, _, _, _ = client.Run(ctx, "uname -m")
|
||||
fmt.Printf(" Architecture: %s\n", strings.TrimSpace(stdout))
|
||||
|
||||
// Memory
|
||||
stdout, _, _, _ = client.Run(ctx, "free -h | grep Mem | awk '{print $2}'")
|
||||
fmt.Printf(" Memory: %s\n", strings.TrimSpace(stdout))
|
||||
|
||||
// Disk
|
||||
stdout, _, _, _ = client.Run(ctx, "df -h / | tail -1 | awk '{print $2 \" total, \" $4 \" available\"}'")
|
||||
fmt.Printf(" Disk: %s\n", strings.TrimSpace(stdout))
|
||||
|
||||
// Docker
|
||||
stdout, _, _, err = client.Run(ctx, "docker --version 2>/dev/null")
|
||||
if err == nil {
|
||||
fmt.Printf(" Docker: %s\n", cli.SuccessStyle.Render(strings.TrimSpace(stdout)))
|
||||
fmt.Printf(" Docker: %s\n", strings.TrimSpace(stdout))
|
||||
} else {
|
||||
fmt.Printf(" Docker: %s\n", cli.DimStyle.Render("not installed"))
|
||||
fmt.Printf(" Docker: not installed\n")
|
||||
}
|
||||
|
||||
// Check if Coolify is running
|
||||
stdout, _, _, _ = client.Run(ctx, "docker ps 2>/dev/null | grep -q coolify && echo 'running' || echo 'not running'")
|
||||
if strings.TrimSpace(stdout) == "running" {
|
||||
fmt.Printf(" Coolify: %s\n", cli.SuccessStyle.Render("running"))
|
||||
fmt.Printf(" Coolify: running\n")
|
||||
} else {
|
||||
fmt.Printf(" Coolify: %s\n", cli.DimStyle.Render("not installed"))
|
||||
fmt.Printf(" Coolify: not installed\n")
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s SSH test passed\n", cli.SuccessStyle.Render("✓"))
|
||||
fmt.Printf("\nSSH test passed\n")
|
||||
|
||||
return nil
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,33 @@
|
|||
package anscmd
|
||||
|
||||
import (
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"dappco.re/go/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.RegisterCommands(AddAnsibleCommands)
|
||||
}
|
||||
// Register registers the 'ansible' command and all subcommands on the given Core instance.
|
||||
func Register(c *core.Core) {
|
||||
c.Command("ansible", core.Command{
|
||||
Description: "Run Ansible playbooks natively (no Python required)",
|
||||
Action: runAnsible,
|
||||
Flags: core.Options{
|
||||
{Key: "inventory", Value: ""},
|
||||
{Key: "limit", Value: ""},
|
||||
{Key: "tags", Value: ""},
|
||||
{Key: "skip-tags", Value: ""},
|
||||
{Key: "extra-vars", Value: ""},
|
||||
{Key: "verbose", Value: 0},
|
||||
{Key: "check", Value: false},
|
||||
},
|
||||
})
|
||||
|
||||
// AddAnsibleCommands registers the 'ansible' command and all subcommands.
|
||||
func AddAnsibleCommands(root *cli.Command) {
|
||||
root.AddCommand(ansibleCmd)
|
||||
c.Command("ansible/test", core.Command{
|
||||
Description: "Test SSH connectivity to a host",
|
||||
Action: runAnsibleTest,
|
||||
Flags: core.Options{
|
||||
{Key: "user", Value: "root"},
|
||||
{Key: "password", Value: ""},
|
||||
{Key: "key", Value: ""},
|
||||
{Key: "port", Value: 22},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ func TestModuleHostname_Bad_MissingName(t *testing.T) {
|
|||
|
||||
```
|
||||
go-ansible/
|
||||
go.mod Module definition (forge.lthn.ai/core/go-ansible)
|
||||
go.mod Module definition (dappco.re/go/core/ansible)
|
||||
go.sum Dependency checksums
|
||||
CLAUDE.md AI assistant context file
|
||||
types.go Core data types and KnownModules registry
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ description: A pure Go Ansible playbook engine -- parses YAML playbooks, invento
|
|||
|
||||
# go-ansible
|
||||
|
||||
`forge.lthn.ai/core/go-ansible` is a pure Go implementation of an Ansible playbook engine. It parses standard Ansible YAML playbooks, inventories, and roles, then executes tasks against remote hosts over SSH -- with no dependency on Python or the upstream `ansible-playbook` binary.
|
||||
`dappco.re/go/core/ansible` is a pure Go implementation of an Ansible playbook engine. It parses standard Ansible YAML playbooks, inventories, and roles, then executes tasks against remote hosts over SSH -- with no dependency on Python or the upstream `ansible-playbook` binary.
|
||||
|
||||
## Module Path
|
||||
|
||||
```
|
||||
forge.lthn.ai/core/go-ansible
|
||||
dappco.re/go/core/ansible
|
||||
```
|
||||
|
||||
Requires **Go 1.26+**.
|
||||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
ansible "forge.lthn.ai/core/go-ansible"
|
||||
ansible "dappco.re/go/core/ansible"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -148,8 +148,8 @@ Both fully-qualified collection names (e.g. `ansible.builtin.shell`) and short-f
|
|||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `forge.lthn.ai/core/cli` | CLI framework (command registration, flags, styled output) |
|
||||
| `forge.lthn.ai/core/go-log` | Structured logging and contextual error helper (`log.E()`) |
|
||||
| `dappco.re/go/core` | Core framework (command registration, flags) |
|
||||
| `dappco.re/go/core/log` | Structured logging and contextual error helper (`log.E()`) |
|
||||
| `golang.org/x/crypto` | SSH protocol implementation (`crypto/ssh`, `crypto/ssh/knownhosts`) |
|
||||
| `gopkg.in/yaml.v3` | YAML parsing for playbooks, inventories, and role files |
|
||||
| `github.com/stretchr/testify` | Test assertions (test-only) |
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// Executor runs Ansible playbooks.
|
||||
|
|
|
|||
36
go.mod
36
go.mod
|
|
@ -1,45 +1,19 @@
|
|||
module forge.lthn.ai/core/go-ansible
|
||||
module dappco.re/go/core/ansible
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
forge.lthn.ai/core/cli v0.3.7
|
||||
forge.lthn.ai/core/go-io v0.1.7
|
||||
forge.lthn.ai/core/go-log v0.0.4
|
||||
dappco.re/go/core v0.5.0
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
forge.lthn.ai/core/go v0.3.3 // indirect
|
||||
forge.lthn.ai/core/go-i18n v0.1.7 // indirect
|
||||
forge.lthn.ai/core/go-inference v0.1.6 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
forge.lthn.ai/core/go-log v0.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.21 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
)
|
||||
|
|
|
|||
70
go.sum
70
go.sum
|
|
@ -1,87 +1,29 @@
|
|||
forge.lthn.ai/core/cli v0.3.7 h1:1GrbaGg0wDGHr6+klSbbGyN/9sSbHvFbdySJznymhwg=
|
||||
forge.lthn.ai/core/cli v0.3.7/go.mod h1:DBUppJkA9P45ZFGgI2B8VXw1rAZxamHoI/KG7fRvTNs=
|
||||
forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0=
|
||||
forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ=
|
||||
forge.lthn.ai/core/go-i18n v0.1.7 h1:aHkAoc3W8fw3RPNvw/UszQbjyFWXHszzbZgty3SwyAA=
|
||||
forge.lthn.ai/core/go-i18n v0.1.7/go.mod h1:0VDjwtY99NSj2iqwrI09h5GUsJeM9s48MLkr+/Dn4G8=
|
||||
forge.lthn.ai/core/go-inference v0.1.6 h1:ce42zC0zO8PuISUyAukAN1NACEdWp5wF1mRgnh5+58E=
|
||||
forge.lthn.ai/core/go-inference v0.1.6/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw=
|
||||
forge.lthn.ai/core/go-io v0.1.7 h1:Tdb6sqh+zz1lsGJaNX9RFWM6MJ/RhSAyxfulLXrJsbk=
|
||||
forge.lthn.ai/core/go-io v0.1.7/go.mod h1:8lRLFk4Dnp5cR/Cyzh9WclD5566TbpdRgwcH7UZLWn4=
|
||||
dappco.re/go/core v0.5.0 h1:P5DJoaCiK5Q+af5UiTdWqUIW4W4qYKzpgGK50thm21U=
|
||||
dappco.re/go/core v0.5.0/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/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
||||
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
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=
|
||||
|
|
|
|||
50
kb/Executor.md
Normal file
50
kb/Executor.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Executor
|
||||
|
||||
Module: `dappco.re/go/core/ansible`
|
||||
|
||||
The `Executor` is the main playbook runner. It manages SSH connections, variable resolution, conditional evaluation, loops, blocks, roles, handlers, and module execution.
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. Parse playbook YAML into `[]Play`
|
||||
2. For each play:
|
||||
- Resolve target hosts from inventory (apply `Limit` filter)
|
||||
- Merge play variables
|
||||
- Gather facts (SSH into hosts, collect OS/hostname/kernel info)
|
||||
- Execute `pre_tasks`, `roles`, `tasks`, `post_tasks`
|
||||
- Run notified handlers
|
||||
3. Each task goes through:
|
||||
- Tag matching (`Tags`, `SkipTags`)
|
||||
- Block/rescue/always handling
|
||||
- Include/import resolution
|
||||
- `when` condition evaluation
|
||||
- Loop expansion
|
||||
- Module execution via SSH
|
||||
- Result registration and handler notification
|
||||
|
||||
## Templating
|
||||
|
||||
Jinja2-like `{{ var }}` syntax is supported:
|
||||
|
||||
- Variable resolution from play vars, task vars, host vars, facts, registered results
|
||||
- Dotted access: `{{ result.stdout }}`, `{{ result.rc }}`
|
||||
- Filters: `| default(value)`, `| bool`, `| trim`
|
||||
- Lookups: `lookup('env', 'HOME')`, `lookup('file', '/path')`
|
||||
|
||||
## Conditionals
|
||||
|
||||
`when` supports:
|
||||
|
||||
- Boolean literals: `true`, `false`
|
||||
- Registered variable checks: `result is success`, `result is failed`, `result is changed`, `result is defined`
|
||||
- Negation: `not condition`
|
||||
- Variable truthiness checks
|
||||
|
||||
## SSH Client Features
|
||||
|
||||
- Key-based and password authentication
|
||||
- Known hosts verification
|
||||
- Privilege escalation (`become`/`sudo`) with password support
|
||||
- File upload via `cat` (no SCP dependency)
|
||||
- File download, stat, exists checks
|
||||
- Context-based timeout and cancellation
|
||||
64
kb/Home.md
Normal file
64
kb/Home.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# go-ansible
|
||||
|
||||
Module: `dappco.re/go/core/ansible`
|
||||
|
||||
Pure Go Ansible executor that parses and runs Ansible playbooks without requiring the Python ansible binary. Supports SSH-based remote execution, inventory parsing, Jinja2-like templating, module execution, roles, handlers, loops, blocks, and conditionals.
|
||||
|
||||
## Architecture
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `types.go` | Data types: `Playbook`, `Play`, `Task`, `TaskResult`, `Inventory`, `Host`, `Facts`, `KnownModules` |
|
||||
| `parser.go` | YAML parsing for playbooks, inventory, roles, and task files |
|
||||
| `executor.go` | Playbook execution engine with SSH client management, templating, conditionals |
|
||||
| `ssh.go` | `SSHClient` for remote command execution, file upload/download |
|
||||
| `modules.go` | Ansible module implementations (shell, copy, template, file, service, etc.) |
|
||||
|
||||
CLI registration in `cmd/ansible/`.
|
||||
|
||||
## Key Types
|
||||
|
||||
### Core Types
|
||||
|
||||
- **`Executor`** — Runs playbooks: `Run()`, `SetInventory()`, `SetVar()`. Supports callbacks: `OnPlayStart`, `OnTaskStart`, `OnTaskEnd`, `OnPlayEnd`. Options: `Limit`, `Tags`, `SkipTags`, `CheckMode`, `Diff`, `Verbose`
|
||||
- **`Parser`** — Parses YAML: `ParsePlaybook()`, `ParseInventory()`, `ParseRole()`, `ParseTasks()`
|
||||
- **`SSHClient`** — SSH operations: `Connect()`, `Run()`, `RunScript()`, `Upload()`, `Download()`, `FileExists()`, `Stat()`, `SetBecome()`
|
||||
- **`SSHConfig`** — Connection config: `Host`, `Port`, `User`, `Password`, `KeyFile`, `Become`, `BecomeUser`, `BecomePass`, `Timeout`
|
||||
|
||||
### Playbook Types
|
||||
|
||||
- **`Play`** — Single play: `Name`, `Hosts`, `Become`, `Vars`, `PreTasks`, `Tasks`, `PostTasks`, `Roles`, `Handlers`
|
||||
- **`Task`** — Single task: `Name`, `Module`, `Args`, `Register`, `When`, `Loop`, `LoopControl`, `Block`, `Rescue`, `Always`, `Notify`, `IncludeTasks`, `ImportTasks`
|
||||
- **`TaskResult`** — Execution result: `Changed`, `Failed`, `Skipped`, `Msg`, `Stdout`, `Stderr`, `RC`, `Results` (for loops)
|
||||
- **`RoleRef`** — Role reference with vars and conditions
|
||||
|
||||
### Inventory Types
|
||||
|
||||
- **`Inventory`** — Top-level with `All` group
|
||||
- **`InventoryGroup`** — `Hosts`, `Children`, `Vars`
|
||||
- **`Host`** — Connection details: `AnsibleHost`, `AnsiblePort`, `AnsibleUser`, `AnsibleSSHPrivateKeyFile`
|
||||
- **`Facts`** — Gathered facts: `Hostname`, `FQDN`, `OS`, `Distribution`, `Architecture`, `Kernel`, `Memory`, `CPUs`
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import "dappco.re/go/core/ansible"
|
||||
|
||||
executor := ansible.NewExecutor("/path/to/playbooks")
|
||||
executor.SetInventory("inventory/hosts.yml")
|
||||
executor.SetVar("deploy_version", "1.2.3")
|
||||
|
||||
executor.OnTaskStart = func(host string, task *ansible.Task) {
|
||||
fmt.Printf("[%s] %s\n", host, task.Name)
|
||||
}
|
||||
|
||||
err := executor.Run(ctx, "deploy.yml")
|
||||
defer executor.Close()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dappco.re/go/core/log` — Structured logging and errors
|
||||
- `golang.org/x/crypto/ssh` — SSH client
|
||||
- `golang.org/x/crypto/ssh/knownhosts` — Host key verification
|
||||
- `gopkg.in/yaml.v3` — YAML parsing
|
||||
|
|
@ -9,8 +9,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// executeModule dispatches to the appropriate module handler.
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"slices"
|
||||
"strings"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
|
|||
4
ssh.go
4
ssh.go
|
|
@ -12,8 +12,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/knownhosts"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue