Basic Sync Example
A runnable Go program that drives the pkg/dataplane Client end-to-end: connect, sync a small HAProxy config, inspect the structured result, and preview an additional change with DryRun.
The program lives in main.go and targets a stand-alone HAProxy + Dataplane API — no Kubernetes cluster required. Use it as a template when scripting one-off configuration pushes, or as a smoke test when investigating Dataplane API connectivity.
Prerequisites
-
An HAProxy binary with a running Dataplane API. Inside the HAProxy config:
program api
command dataplaneapi -f /etc/haproxy/dataplaneapi.yml
-
A dataplaneapi.yml that exposes the API on a port this program can reach:
dataplaneapi:
host: 0.0.0.0
port: 5555
user:
- insecure: true
password: admin
name: admin
The example reads three environment variables with sensible defaults; override them for your environment:
export HAPROXY_URL=http://localhost:5555/v3 # /v3 only — the library detects v3.0/v3.1/v3.2/v3.3 at startup. Trailing /v2 is silently rewritten to /v3 for the version-detection probe but no v2-only operations are supported.
export HAPROXY_USER=admin
export HAPROXY_PASS=admin
go run main.go
Expected Output
Creating dataplane client...
Connected to HAProxy at http://localhost:5555/v3
Syncing HAProxy configuration...
Sync completed successfully!
Duration: 1.234s
Operations applied: 5
Sync mode: reload
HAProxy reloaded: reload-123
Applied operations:
1. [create] web-servers: Created backend
2. [create] web-servers/web1: Created server
3. [create] web-servers/web2: Created server
4. [create] http-in: Created frontend
5. [update] global: Updated global settings
--- Dry Run Example ---
Would apply 1 operations:
1. [create] web-servers/web3: Would create server
Example completed successfully!
HAProxy reloaded only appears when the Dataplane API returns HTTP 202 (structural change requiring a reload); purely runtime-API updates (weight/address/port/maintenance) print "No HAProxy reload required".
What the Program Demonstrates
All four patterns below are exactly what pkg/dataplane exposes publicly — if you're writing code against the controller's sync library elsewhere, the snippets in main.go are good starting points.
1. Reusable client
client, err := dataplane.NewClient(ctx, &endpoint)
if err != nil {
return err
}
defer client.Close()
r1, _ := client.Sync(ctx, config1, nil, nil)
r2, _ := client.Sync(ctx, config2, nil, nil)
NewClient takes a pointer (*Endpoint) and performs one round-trip to /v3/info to detect the Dataplane API version. Reuse the returned *Client for every subsequent call.
2. Structured errors
result, err := client.Sync(ctx, desired, nil, opts)
if err != nil {
if syncErr, ok := errors.AsType[*dataplane.SyncError](err); ok {
log.Printf("failed at %s: %s", syncErr.Stage, syncErr.Message)
for _, hint := range syncErr.Hints {
log.Printf(" hint: %s", hint)
}
}
return err
}
errors.AsType[T] is the Go 1.26 generic helper used in main.go. The traditional var syncErr *T; errors.As(err, &syncErr) two-liner still works if you're targeting an older toolchain.
SyncError.Stage indicates where the failure occurred — connection setup (connect), config parsing (parse-current / parse-desired), structural comparison (compare, compare_ssl, compare_maps, …), the per-type pre-config aux upload (sync_ssl_pre, sync_maps_pre, …), the transactional apply / commit (apply, commit), reload verification (reload_verification), or the raw-config fallback (fallback). The full list lives on the SyncError.Stage godoc. Hints are actionable suggestions emitted by the library.
3. Inspecting the result
fmt.Printf("applied %d operations in %v\n", len(result.AppliedOperations), result.Duration)
if result.ReloadTriggered {
fmt.Printf("reload: %s\n", result.ReloadID)
}
if result.SyncMode == dataplane.SyncModeRuntime {
fmt.Println("applied via runtime API (no reload)")
}
for _, op := range result.AppliedOperations {
fmt.Printf("[%s] %s: %s\n", op.Type, op.Resource, op.Description)
}
4. Dry run
diff, err := client.DryRun(ctx, candidateConfig)
if err != nil {
return err
}
if diff.HasChanges {
fmt.Printf("would apply %d operations\n", len(diff.PlannedOperations))
}
DryRun runs the full compare pipeline but skips the apply step; handy for CI preflight checks.
See Also
License
Apache-2.0 — see root LICENSE.