175 lines
5.9 KiB
Markdown
175 lines
5.9 KiB
Markdown
---
|
|
title: Development
|
|
description: How to build, test, and contribute to go-ansible.
|
|
---
|
|
|
|
# Development
|
|
|
|
## Prerequisites
|
|
|
|
- **Go 1.26+** (the module requires Go 1.26 features)
|
|
- SSH access to a test host (for integration testing, not required for unit tests)
|
|
|
|
## Building
|
|
|
|
The package is a library -- there is no standalone binary. The CLI integration lives in `cmd/ansible/` and is compiled as part of the `core` CLI binary.
|
|
|
|
```bash
|
|
# Verify the module compiles
|
|
go build ./...
|
|
|
|
# If working within the Go workspace
|
|
go work sync
|
|
```
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
# Run all tests
|
|
go test ./...
|
|
|
|
# Run tests with race detection
|
|
go test -race ./...
|
|
|
|
# Run a specific test
|
|
go test -run TestParsePlaybook_Good_SimplePlay
|
|
|
|
# Run tests with verbose output
|
|
go test -v ./...
|
|
```
|
|
|
|
The test suite uses a mock SSH client infrastructure (`mock_ssh_test.go`) to test module handlers without requiring real SSH connections. Tests are organised into separate files by category:
|
|
|
|
| File | Coverage |
|
|
|------|----------|
|
|
| `types_test.go` | YAML unmarshalling for `Task`, `RoleRef`, `Inventory`, `Facts` |
|
|
| `parser_test.go` | Playbook, inventory, and task file parsing |
|
|
| `executor_test.go` | Executor lifecycle, condition evaluation, templating, loops, tag filtering |
|
|
| `ssh_test.go` | SSH client construction and defaults |
|
|
| `mock_ssh_test.go` | Mock SSH infrastructure for module tests |
|
|
| `modules_cmd_test.go` | Command modules: `shell`, `command`, `raw`, `script` |
|
|
| `modules_file_test.go` | File modules: `copy`, `template`, `file`, `lineinfile`, `stat`, `slurp`, `fetch`, `get_url` |
|
|
| `modules_svc_test.go` | Service modules: `service`, `systemd`, `user`, `group` |
|
|
| `modules_infra_test.go` | Infrastructure modules: `apt`, `pip`, `git`, `unarchive`, `ufw`, `docker_compose` |
|
|
| `modules_adv_test.go` | Advanced modules: `debug`, `fail`, `assert`, `set_fact`, `pause`, `wait_for`, `uri`, `blockinfile`, `cron`, `hostname`, `sysctl`, `reboot` |
|
|
|
|
### Test Naming Convention
|
|
|
|
Tests follow the `_Good` / `_Bad` / `_Ugly` suffix pattern:
|
|
|
|
- **`_Good`** -- Happy path: valid inputs produce expected outputs
|
|
- **`_Bad`** -- Expected error conditions: invalid inputs are handled gracefully
|
|
- **`_Ugly`** -- Edge cases: panics, nil inputs, boundary conditions
|
|
|
|
Example:
|
|
|
|
```go
|
|
func TestParsePlaybook_Good_SimplePlay(t *testing.T) { ... }
|
|
func TestParsePlaybook_Bad_MissingFile(t *testing.T) { ... }
|
|
func TestParsePlaybook_Bad_InvalidYAML(t *testing.T) { ... }
|
|
```
|
|
|
|
## Code Organisation
|
|
|
|
The package is intentionally flat -- a single `ansible` package with no sub-packages. This keeps the API surface small and avoids circular dependencies.
|
|
|
|
When adding new functionality:
|
|
|
|
- **New module**: Add a `module{Name}` method to `Executor` in `modules.go`, add the case to the `executeModule` switch statement, and add the module name to `KnownModules` in `types.go` (both FQCN and short forms). Write tests in the appropriate `modules_*_test.go` file.
|
|
- **New parser feature**: Extend the relevant `Parse*` method in `parser.go`. If it involves new YAML keys on `Task`, update the `knownKeys` map in `UnmarshalYAML` to prevent them from being mistakenly identified as module names.
|
|
- **New type**: Add to `types.go` with appropriate YAML and JSON struct tags.
|
|
|
|
## Coding Standards
|
|
|
|
- **UK English** in comments and documentation (colour, organisation, centre).
|
|
- All functions must have typed parameters and return types.
|
|
- Use `log.E(scope, message, err)` from `go-log` for contextual errors in SSH and parser code.
|
|
- Use `fmt.Errorf` with `%w` for wrapping errors in the executor.
|
|
- Test assertions use `testify/assert` (soft) and `testify/require` (hard, stops test on failure).
|
|
|
|
## Adding a New Module
|
|
|
|
Here is a walkthrough for adding a hypothetical `ansible.builtin.hostname` module (which already exists -- this is illustrative):
|
|
|
|
### 1. Register the module name
|
|
|
|
In `types.go`, add both forms to `KnownModules`:
|
|
|
|
```go
|
|
var KnownModules = []string{
|
|
// ...existing entries...
|
|
"ansible.builtin.hostname",
|
|
// ...
|
|
"hostname",
|
|
}
|
|
```
|
|
|
|
### 2. Add the dispatch case
|
|
|
|
In `modules.go`, inside `executeModule`:
|
|
|
|
```go
|
|
case "ansible.builtin.hostname":
|
|
return e.moduleHostname(ctx, client, args)
|
|
```
|
|
|
|
### 3. Implement the handler
|
|
|
|
```go
|
|
func (e *Executor) moduleHostname(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
|
|
name := getStringArg(args, "name", "")
|
|
if name == "" {
|
|
return nil, errors.New("hostname: name is required")
|
|
}
|
|
|
|
cmd := fmt.Sprintf("hostnamectl set-hostname %q", name)
|
|
stdout, stderr, rc, err := client.Run(ctx, cmd)
|
|
if err != nil {
|
|
return &TaskResult{Failed: true, Msg: err.Error(), Stderr: stderr, RC: rc}, nil
|
|
}
|
|
if rc != 0 {
|
|
return &TaskResult{Failed: true, Msg: stderr, Stdout: stdout, Stderr: stderr, RC: rc}, nil
|
|
}
|
|
|
|
return &TaskResult{Changed: true, Msg: fmt.Sprintf("hostname set to %s", name)}, nil
|
|
}
|
|
```
|
|
|
|
### 4. Write tests
|
|
|
|
In the appropriate `modules_*_test.go` file, using the mock SSH infrastructure:
|
|
|
|
```go
|
|
func TestModuleHostname_Good(t *testing.T) {
|
|
// Use mock SSH client to verify the command is constructed correctly
|
|
// ...
|
|
}
|
|
|
|
func TestModuleHostname_Bad_MissingName(t *testing.T) {
|
|
// Verify that omitting the name argument returns an error
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Project Structure Reference
|
|
|
|
```
|
|
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
|
|
parser.go YAML parsing (playbooks, inventories, roles)
|
|
executor.go Execution engine (orchestration, templating, conditions)
|
|
modules.go 49 module handler implementations
|
|
ssh.go SSH client (auth, commands, file transfer, become)
|
|
*_test.go Test files (see table above)
|
|
cmd/
|
|
ansible/
|
|
cmd.go CLI command registration via core/cli
|
|
ansible.go CLI implementation (flags, runner, test subcommand)
|
|
```
|
|
|
|
## Licence
|
|
|
|
EUPL-1.2
|