package ansible import ( "bytes" "context" "fmt" "os" "os/exec" "path/filepath" "sync" ) // Executor handles running Ansible playbooks type Executor struct { workDir string grpcServerHost string grpcServerPort string backendURL string } // ExecutorConfig holds configuration for the Executor type ExecutorConfig struct { WorkDir string GRPCServerHost string GRPCServerPort string BackendURL string } // NewExecutor creates a new Ansible executor func NewExecutor(cfg ExecutorConfig) *Executor { return &Executor{ workDir: cfg.WorkDir, grpcServerHost: cfg.GRPCServerHost, grpcServerPort: cfg.GRPCServerPort, backendURL: cfg.BackendURL, } } // DeployResult holds the result of a deployment type DeployResult struct { Host string Success bool Stdout string Stderr string Err error } // WorkDir returns the work directory path func (e *Executor) WorkDir() string { return e.workDir } // Deploy runs Ansible playbook for the given inventory func (e *Executor) Deploy( ctx context.Context, inventoryPath string, deployType string, ) ([]DeployResult, error) { playbookName := "binary_deploy.yml" if deployType == "docker" { playbookName = "docker_deploy.yml" } playbookPath := filepath.Join(e.workDir, playbookName) cmd := exec.CommandContext(ctx, "ansible-playbook", "-i", inventoryPath, "-e", fmt.Sprintf("backend_url=%s", e.backendURL), playbookPath, ) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr runErr := cmd.Run() // Parse results per host (simplified - returns single result for all) return []DeployResult{ { Host: "all", Success: runErr == nil, Stdout: stdout.String(), Stderr: stderr.String(), Err: runErr, }, }, nil } // DeployParallel runs Ansible playbook for multiple inventories in parallel func (e *Executor) DeployParallel( ctx context.Context, inventoryPaths []string, deployType string, ) (map[string][]DeployResult, error) { var wg sync.WaitGroup results := make(map[string][]DeployResult) errCh := make(chan error, len(inventoryPaths)) for _, path := range inventoryPaths { wg.Add(1) go func(p string) { defer wg.Done() res, err := e.Deploy(ctx, p, deployType) if err != nil { errCh <- err } results[p] = res }(path) } wg.Wait() close(errCh) // Collect errors var errs []error for err := range errCh { errs = append(errs, err) } if len(errs) > 0 { return results, fmt.Errorf("some deployments failed: %v", errs) } return results, nil } // WritePlaybook writes a playbook to the work directory func (e *Executor) WritePlaybook(name string, content string) error { path := filepath.Join(e.workDir, name) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } return os.WriteFile(path, []byte(content), 0644) } // WriteAllPlaybooks writes all playbooks to the work directory func (e *Executor) WriteAllPlaybooks() error { if err := e.WritePlaybook("binary_deploy.yml", BinaryDeployPlaybook); err != nil { return err } return e.WritePlaybook("docker_deploy.yml", DockerDeployPlaybook) }