- Go 97.3%
- Makefile 2.7%
| cmd/gghc | ||
| docs | ||
| internal | ||
| testdata | ||
| .gitignore | ||
| .golangci.yml | ||
| config.example.json | ||
| FILEMAP.md | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
gogohalocmd
A headless Go CLI that talks to HaloITSM. Ships as the binary gghc
(gghc.exe on Windows). Intended for Windows Service Recovery Actions
and similar automation: runs under a service account, succeeds or fails
fast, emits structured JSONL logs, and returns a classifying exit code
so callers can branch.
Naming: project name is gogohalocmd; the binary is
gghc; the Go module path isgghc.pcdoyle.dev.
Status
Pre-v1. Implemented and working end-to-end today:
gghc auth test— fetches an OAuth2 token and reports success/failure.gghc ticket get --ticket-id N [--include-actions]— reads a ticket and prints id, summary, status (id + name), and assigned agent (id + name). Pass--include-actionsto also fetch the most recent note; that adds one HTTP round trip, so default runs stay fast. Accepts leading zeros (0103291→103291).
Deferred (planned, not yet shipped):
gghc ticket create/gghc ticket note(writes).- HTTP retry + backoff on 429 / 5xx. The 401 → token-refresh → single
retry path is already wired via
RefreshableTokenSource.
See plans/gghc-v1-plan.md for the full design.
Quick start
Requires Go 1.26.1.
# Build a host-native sandbox with a copy of config.example.json:
make testenv
# Edit testenv/config.json with your tenant values (base_url, client_id).
# Secrets come from env vars — never in the file committed to git.
export GGHC_CLIENT_SECRET='your-halo-client-secret'
# Verify auth works without creating any data:
./testenv/gghc auth test
# Read a ticket:
./testenv/gghc ticket get --ticket-id 12345
./testenv/gghc --json ticket get --ticket-id 12345
make testenv preserves an existing testenv/config.json — it only
refreshes the binary.
Configuration
Precedence (highest wins): CLI flags → GGHC_* env vars → JSON file →
built-in defaults.
The config file is config.json. Lookup order when --config is not
supplied:
- Next to the binary (
<exe-dir>/config.json) — the deployment convention. ./config.jsonin the current working directory.%ProgramData%\gghc\config.json(Windows) /$XDG_CONFIG_HOME/gghc/config.jsonor~/.config/gghc/config.json, then/etc/gghc/config.json(POSIX).
config.example.json is annotated in place; copy
it to config.json and edit.
Auth
Two OAuth2 grants are supported:
| Grant | Use when | Required config |
|---|---|---|
client_credentials (default) |
pure machine-to-machine | client_id + GGHC_CLIENT_SECRET |
password |
ticket actions must be attributed to an Agent identity | client_id + username + GGHC_AGENT_PASSWORD |
Tokens are cached to disk (<state-dir>/token.json, 0600) between
invocations and reused until expires_at − early_refresh_seconds. The
cache is invalidated automatically when the ticket endpoint returns 401,
and by a config change (method / client_id / username).
Do not commit secrets. Prefer the env vars (GGHC_CLIENT_SECRET,
GGHC_AGENT_PASSWORD); inline JSON secrets work but emit a warning.
Logging
Structured JSONL records go to a rotating file only — never to stdout or stderr. The console is reserved for the result (stdout) and a single plain-text error line on failure (stderr); operators reading gghc.jsonl get the structured story without console pollution.
- Active file:
<log-dir>/gghc.jsonl. - Rotated files:
gghc-{timestamp}.jsonl(.gz)via lumberjack v2. - Default log dir:
<exe-dir>/logs/on POSIX when writable,%ProgramData%\gghc\logs\on Windows, else$XDG_STATE_HOME/gghc/logs. - Override via
--log-dir,GGHC_LOG_DIR, orlogging.dirin config. - Set
logging.file = false(orGGHC_LOG_FILE=false) to disable file output entirely; structured records are then silently discarded (no console fallback by design). - Rotation defaults: 10MB per file, 5 backups, 30-day retention, gzip.
- If the log directory can't be created, the CLI does not fail: it
prints one plain-text warning to stderr (
gghc: warning: file logging disabled: <err>) and discards structured records for the rest of the run.
Every record carries correlation_id (UUIDv4 per invocation) and
version. Values under keys matching /(?i)(secret|token|password|authorization)/
are replaced with [REDACTED].
Version string
gghc --version prints gogohalocmd (v{MAJOR}.{YYYYMM}.{BUILD}), e.g.
gogohalocmd (v0.202604.003). Build number = count of commits on HEAD
since the 1st of the current UTC month. Override with VERSION_MAJOR=N
or set VERSION=vX.YYYYMM.NNN in the environment at make build time.
Exit codes
These are the contract with Windows Service Recovery Actions; automation branches on the numeric code.
| Code | Name | Trigger |
|---|---|---|
| 0 | OK | success |
| 1 | Generic | unclassified |
| 2 | Config | missing required field, bad JSON, validation |
| 3 | Auth | token fetch failed, 401, missing secret |
| 4 | Validation | Halo 400/422, client-side required-field miss, unknown ticket id |
| 5 | API | Halo 5xx after retries |
| 6 | RateLimit | 429 after retries |
| 7 | Timeout | context deadline, network timeout |
| 8 | IO | log dir unwritable after fallbacks, config unreadable |
Development
make build # ./bin/gghc for the host platform
make build-linux # cross-compile for linux/amd64
make build-windows # cross-compile for windows/amd64
make build-all # both release targets
make test # go test ./...
make race # go test -race ./...
make vet # go vet ./...
make lint # golangci-lint run
make testenv # stage ./testenv/gghc + config.json (idempotent)
make clean # remove ./bin/ (leaves ./testenv/ alone)
make clean-testenv # remove ./testenv/ entirely
All build targets write to ./bin/. The Makefile stamps the binary with
Version via -ldflags.
Layout
cmd/gghc/ CLI entry: main, root flags, runAction shared bookend, subcommand actions
internal/ implementation (not importable by other modules)
app/ glue layer: action funcs that own success-path logging (GetTicket; create/note land with the write slice)
halo/ HaloITSM REST client: auth, transport, tickets, models, errors
config/ JSON + env load, validate
logging/ slog JSON handler + lumberjack rotation + key-based redaction (file-only sink)
secret/ redacting Value + Resolver chain (env → JSON sources)
paths/ OS-aware resolution of config / state / logs dirs
exitcode/ sentinel errors + numeric mapping (the contract with Recovery Actions)
version/ ldflags-injected version string
docs/ HaloITSM REST docs (apidoc + swagger.json, reference only)
plans/ design docs — gghc-v1-plan.md is the authoritative spec
testdata/ golden fixtures for halo-package tests (write-slice payloads)
.golangci.yml pinned linter set (errcheck, govet, staticcheck, errorlint, gocritic, …)
.editorconfig cross-editor consistency (tabs/EOL/trim)
FILEMAP.md one-line description per tracked file
See FILEMAP.md for a one-line description of every file.
References
- plans/gghc-v1-plan.md — authoritative design spec. If you change an architectural decision, update the plan.
- docs/apidoc/ — pre-rendered HaloITSM REST docs. Use
this over
docs/swagger.jsonfor readability; the two should agree. - CLAUDE.md — orientation for AI coding assistants.