Skip to content

feat: Go harness CLI with provider validation#12

Merged
robbycochran merged 6 commits into
mainfrom
rc-go-harness
Jun 4, 2026
Merged

feat: Go harness CLI with provider validation#12
robbycochran merged 6 commits into
mainfrom
rc-go-harness

Conversation

@robbycochran

@robbycochran robbycochran commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Cobra CLI replacing the bash bin/harness dispatcher. All 7 subcommands: new, connect, deploy, teardown, preflight, providers, test.
  • harness new --local implemented natively in Go — parses profile TOML, validates profile providers against the gateway (✓/✗ per provider), stages env + GWS creds, creates sandbox with retry.
  • Gateway interface (internal/gateway/) abstracts all OpenShell CLI calls behind an interface. CLI implementation shells out to openshell; swap to gRPC later by changing one line.
  • Provider validation — checks each provider in the profile is registered on the gateway before sandbox creation. Missing providers reported and skipped.
  • Launcher fixes — metadata.json read-then-patch (preserves CLI fields), skip K8s dotfile symlinks, UBI9 base with openssh-clients (openshell CLI needs ssh + glibc).
  • Comprehensive test suite — 64 unit tests + dual-path integration across {bash,go} × {podman,ocp}.

Package structure

main.go                        # Cobra root, harness dir detection
cmd/                           # One file per subcommand
  new.go / new_test.go         # Native local, bash remote, 7 orchestration tests
  connect.go                   # Exec into openshell sandbox connect
  deploy.go / teardown.go / preflight.go / providers.go / test.go  # Bash wrappers
internal/gateway/
  gateway.go                   # Interface: InferenceGet, ProviderGet/List, SandboxCreate/Delete/Connect/Upload/Exec
  cli.go / cli_test.go         # CLI implementation with 11 stub-based tests
internal/profile/
  parse.go / profile.go        # Config struct, TOML parsing, ValidateProviders, StageHarnessDir
  profile_test.go              # 10 tests including mock gateway validation
internal/runner/
  runner.go                    # RunScript helper for bash wrappers

Test results

Path Result
Unit tests (Go + bats) 64/64
bash + podman (full) 19/19
Go + podman (full) 19/19
bash + OCP (full) 17/17

Test targets

make test-unit       # Go unit tests + bats (no gateway needed)
make test-podman     # bash + podman full lifecycle
make test-go-podman  # Go + podman full lifecycle
make test-ocp        # bash + OCP full lifecycle
make test-go-ocp     # Go + OCP full lifecycle
make test-all        # all 4 combinations

Test plan

  • make test-unit — 64/64 pass
  • make test-podman — 19/19 pass (bash + podman)
  • test-flow.sh podman --full --go — 19/19 pass (Go + podman)
  • test-flow.sh ocp --full — 17/17 pass (bash + OCP)
  • test-flow.sh ocp --full --go — not yet run (Go + OCP)
  • Error scenarios: bad profile, teardown idempotency, missing providers

🤖 Generated with Claude Code

robbycochran and others added 6 commits June 4, 2026 13:10
…alidation

Cobra CLI replacing the bash bin/harness dispatcher. All 7 subcommands
registered: new, connect, deploy, teardown, preflight, providers, test.

The `new --local` path is implemented natively in Go — parses profile
TOML, validates that profile providers are registered on the gateway
(✓/✗ for each), stages env + GWS creds, creates sandbox with retry.
Remote path and other subcommands delegate to existing bash scripts.

Package structure:
- internal/profile/ — Config struct, TOML parsing, ValidateProviders,
  StageHarnessDir (6 unit tests)
- internal/runner/ — RunScript, RunCLI, Exec helpers for os/exec
- cmd/ — one file per subcommand

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All OpenShell CLI calls now go through the gateway.Gateway interface:
InferenceGet, ProviderGet, ProviderList, SandboxCreate, SandboxDelete,
SandboxConnect, SandboxUpload, SandboxExec.

gateway.CLI implements the interface by shelling out to the openshell
binary. A future gateway.GRPC implementation can swap in by changing
one line (NewCLI → NewGRPC).

ValidateProviders now takes a gateway.Gateway instead of a CLI path
string. Tests use a mockGateway (3 new test cases).

Runner package simplified to just RunScript (bash wrapper helper).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unit tests (no gateway needed):
- 11 gateway CLI tests — stub-based: output parsing, arg building,
  provider get/list, inference check, sandbox create args
- 7 command tests — mock gateway: no gateway, missing providers,
  all providers missing, bad profile, retry logic, create opts
- 10 profile tests (existing)

Integration tests (test-flow.sh):
- --go flag runs same flow through Go binary instead of bash scripts
- step_fail helper for expected-failure assertions
- Error scenarios: bad profile, no gateway, teardown idempotency,
  missing providers (all run in both bash and Go paths)
- Full matrix: {bash,go} x {podman,ocp} via make targets

Makefile:
- test-unit: fast unit tests (Go + bats, no gateway)
- test-go-podman / test-go-ocp: Go binary integration
- test-all: all 4 combinations with image rebuilds

Total: 28 Go harness tests + 7 launcher tests + 29 bats = 64 unit tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes discovered by OCP integration tests:

1. Dockerfile: use UBI9-minimal + openssh-clients (not scratch/distroless).
   The openshell CLI is dynamically linked against glibc and shells out
   to ssh for sandbox connections.

2. metadata.json: read-then-patch instead of overwrite. The openshell CLI
   writes fields like gateway_type that it needs later. Overwriting with
   only gateway_endpoint + auth_mode caused "Unknown gateway" errors.

3. K8s volume mounts: skip dotfiles (..data, ..2024_*) when copying from
   Secret-mounted directories. These are K8s internal symlinks.

4. Job YAML: add emptyDir volume for /tmp so the launcher has a writable
   tmpdir regardless of OpenShift security policies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UBI9 has a writable /tmp by default — the emptyDir volume mount and
Go-side os.MkdirAll(home) were only needed for scratch/distroless
bases. Revert staging dir to /tmp/openshell.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@robbycochran robbycochran merged commit 7585cbf into main Jun 4, 2026
@robbycochran robbycochran deleted the rc-go-harness branch June 13, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant