No description
  • Go 97.3%
  • Makefile 2.7%
Find a file
2026-05-10 22:07:00 +00:00
cmd/gghc Refactor 2026-04-25 10:43:04 -07:00
docs Include API Docs 2026-04-24 21:04:22 -07:00
internal Refactor 2026-04-25 10:43:04 -07:00
testdata Refactor 2026-04-25 10:43:04 -07:00
.gitignore Include API Docs 2026-04-24 21:04:22 -07:00
.golangci.yml Refactor 2026-04-25 10:43:04 -07:00
config.example.json Added models 2026-04-21 23:59:18 -07:00
FILEMAP.md Refactor 2026-04-25 10:43:04 -07:00
go.mod Added models 2026-04-21 23:59:18 -07:00
go.sum Added models 2026-04-21 23:59:18 -07:00
Makefile Reorganization of Code 2026-04-22 13:12:50 -07:00
README.md Refactor 2026-04-25 10:43:04 -07:00

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 is gghc.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-actions to also fetch the most recent note; that adds one HTTP round trip, so default runs stay fast. Accepts leading zeros (0103291103291).

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:

  1. Next to the binary (<exe-dir>/config.json) — the deployment convention.
  2. ./config.json in the current working directory.
  3. %ProgramData%\gghc\config.json (Windows) / $XDG_CONFIG_HOME/gghc/config.json or ~/.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, or logging.dir in config.
  • Set logging.file = false (or GGHC_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.json for readability; the two should agree.
  • CLAUDE.md — orientation for AI coding assistants.