go-ansible/cmd/ansible/ansible.go

240 lines
6.2 KiB
Go
Raw Normal View History

package anscmd
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
"dappco.re/go/core"
"dappco.re/go/core/ansible"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
// 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
}
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)}
}
playbookPath := positional[0]
// Resolve playbook path
if !filepath.IsAbs(playbookPath) {
playbookPath, _ = filepath.Abs(playbookPath)
}
if !coreio.Local.Exists(playbookPath) {
return core.Result{Value: coreerr.E("runAnsible", fmt.Sprintf("playbook not found: %s", playbookPath), nil)}
}
// Create executor
basePath := filepath.Dir(playbookPath)
executor := ansible.NewExecutor(basePath)
defer executor.Close()
// Set options
executor.Limit = opts.String("limit")
executor.CheckMode = opts.Bool("check")
executor.Verbose = opts.Int("verbose")
if tags := opts.String("tags"); tags != "" {
executor.Tags = strings.Split(tags, ",")
}
if skipTags := opts.String("skip-tags"); skipTags != "" {
executor.SkipTags = strings.Split(skipTags, ",")
}
// Parse extra vars
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 invPath := opts.String("inventory"); invPath != "" {
if !filepath.IsAbs(invPath) {
invPath, _ = filepath.Abs(invPath)
}
if !coreio.Local.Exists(invPath) {
return core.Result{Value: coreerr.E("runAnsible", fmt.Sprintf("inventory not found: %s", invPath), nil)}
}
if coreio.Local.IsDir(invPath) {
for _, name := range []string{"inventory.yml", "hosts.yml", "inventory.yaml", "hosts.yaml"} {
p := filepath.Join(invPath, name)
if coreio.Local.Exists(p) {
invPath = p
break
}
}
}
if err := executor.SetInventory(invPath); err != nil {
return core.Result{Value: coreerr.E("runAnsible", "load inventory", err)}
}
}
// Set up callbacks
executor.OnPlayStart = func(play *ansible.Play) {
fmt.Printf("\nPLAY [%s]\n", play.Name)
fmt.Println(strings.Repeat("*", 70))
}
executor.OnTaskStart = func(host string, task *ansible.Task) {
taskName := task.Name
if taskName == "" {
taskName = task.Module
}
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"
if result.Failed {
status = "failed"
} else if result.Skipped {
status = "skipping"
} else if result.Changed {
status = "changed"
}
fmt.Printf("%s: [%s]", status, host)
if result.Msg != "" && executor.Verbose > 0 {
fmt.Printf(" => %s", result.Msg)
}
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", result.Stderr)
}
if executor.Verbose > 1 {
if result.Stdout != "" {
fmt.Printf("stdout: %s\n", strings.TrimSpace(result.Stdout))
}
}
}
executor.OnPlayEnd = func(play *ansible.Play) {
fmt.Println()
}
// Run playbook
ctx := context.Background()
start := time.Now()
fmt.Printf("Running playbook: %s\n", playbookPath)
if err := executor.Run(ctx, playbookPath); err != nil {
return core.Result{Value: coreerr.E("runAnsible", "playbook failed", err)}
}
fmt.Printf("\nPlaybook completed in %s\n", time.Since(start).Round(time.Millisecond))
return core.Result{OK: true}
}
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", host)
cfg := ansible.SSHConfig{
Host: host,
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 core.Result{Value: coreerr.E("runAnsibleTest", "create client", err)}
}
defer func() { _ = client.Close() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Test connection
start := time.Now()
if err := client.Connect(ctx); err != nil {
return core.Result{Value: coreerr.E("runAnsibleTest", "connect failed", err)}
}
connectTime := time.Since(start)
fmt.Printf("Connected in %s\n", connectTime.Round(time.Millisecond))
// Gather facts
fmt.Println("\nGathering facts...")
stdout, _, _, _ := client.Run(ctx, "hostname -f 2>/dev/null || hostname")
fmt.Printf(" Hostname: %s\n", strings.TrimSpace(stdout))
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))
}
stdout, _, _, _ = client.Run(ctx, "uname -r")
fmt.Printf(" Kernel: %s\n", strings.TrimSpace(stdout))
stdout, _, _, _ = client.Run(ctx, "uname -m")
fmt.Printf(" Architecture: %s\n", strings.TrimSpace(stdout))
stdout, _, _, _ = client.Run(ctx, "free -h | grep Mem | awk '{print $2}'")
fmt.Printf(" Memory: %s\n", strings.TrimSpace(stdout))
stdout, _, _, _ = client.Run(ctx, "df -h / | tail -1 | awk '{print $2 \" total, \" $4 \" available\"}'")
fmt.Printf(" Disk: %s\n", strings.TrimSpace(stdout))
stdout, _, _, err = client.Run(ctx, "docker --version 2>/dev/null")
if err == nil {
fmt.Printf(" Docker: %s\n", strings.TrimSpace(stdout))
} else {
fmt.Printf(" Docker: not installed\n")
}
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: running\n")
} else {
fmt.Printf(" Coolify: not installed\n")
}
fmt.Printf("\nSSH test passed\n")
return core.Result{OK: true}
}