diff --git a/docs/index.yml b/docs/index.yml index edff95343f..0e4763f4f2 100644 --- a/docs/index.yml +++ b/docs/index.yml @@ -108,6 +108,8 @@ navigation: - section: Operations contents: + - page: nicocli Reference + path: manuals/nicocli-reference.md - page: NVLink Partitioning path: manuals/nvlink_partitioning.md - page: Release Instance API Enhancements diff --git a/docs/manuals/nicocli-reference.md b/docs/manuals/nicocli-reference.md new file mode 100644 index 0000000000..444cc15429 --- /dev/null +++ b/docs/manuals/nicocli-reference.md @@ -0,0 +1,469 @@ +# nicocli Reference + +Operator reference for `nicocli`, the CLI for the NICo REST API. Covers installation, configuration, authentication, command mechanics, output formatting, debugging, and TUI mode. Targeted at operators who have completed the [Quick Start Guide](../getting-started/quick-start.md) and want to drive NICo from scripts or one-off interactive sessions. + +The [Day One Operations](day_one_operations.md) guide uses this reference for all CLI mechanics. Read this page first if you plan to script anything beyond the Quick Start happy path. + +## Installation + +Build and install from the `infra-controller-rest` repo: + +``` +make nico-cli # installs to $(go env GOPATH)/bin/nicocli +make nico-cli INSTALL_DIR=/usr/local/bin # install elsewhere +``` + +Verify: + +``` +nicocli --version +``` + +If `nicocli` is not on `$PATH`, add `$(go env GOPATH)/bin` to it. + +> **Note on CLI naming**: older docs and shipped binaries reference `carbidecli` (built via `make carbide-cli`). It's the same source under a previous name; the binary was renamed during the transition from carbide-core to NICo. The Makefile retains both `make nico-cli` and `make carbide-cli` targets. Prefer `nicocli` for new work. + +## Configuration + +### Config file location + +Default: `~/.nico/config.yaml`. Create one with `nicocli init` (errors if a file already exists). Override the path per-command: + +| Override | Mechanism | +|----------|-----------| +| Per command | `--config ` | +| Per shell session | `export NICO_CONFIG=` | +| Per environment (TUI) | Selected interactively from the config picker | + +The config is written with `0600` permissions (private to the owning user). nicocli writes tokens and refresh tokens back to the active config on every successful login or auto-refresh, so the permissions matter. + +### Multi-environment configs + +The TUI auto-discovers any file matching `~/.nico/config*.yaml`. Use one file per environment, naming the file after the environment so the picker is self-documenting: + +``` +~/.nico/config.yaml # default +~/.nico/config.local.yaml # local kind dev +~/.nico/config.staging.yaml # shared staging +~/.nico/config.prod.yaml # production +``` + +For non-interactive commands, pass `--config `. For TUI, start `nicocli tui` and pick from the list. + +### `api.base`, `api.org`, `api.name` + +| Field | Meaning | +|-------|---------| +| `api.base` | Base URL of the REST API server (`http://localhost:8388`, `https://nico.example.com`, etc.) | +| `api.org` | Org name in API paths (`/v2/org///...`) -- must match the org claim in your token | +| `api.name` | API path segment between org and resource. Defaults to `nico` in fresh installs; some deployments override this to a deployment-specific value. | + +`api.name` is the most common source of misconfiguration. If every command returns: + +``` +HTTP/1.1 404 Not Found +{"message":"The requested path could not be found"} +``` + +your `api.name` does not match the deployment. Find the deployment's expected value in the API server's running config (the `api.name` key in the carbide-rest-api configmap for `carbide-rest`-namespace deployments) and set it in your config. + +### Sample config + +```yaml +api: + base: https://nico.example.com + org: my-org + name: nico + +auth: + # Choose ONE auth method. nicocli login picks based on what's present. + + # Static bearer token (no login required; refreshed manually). + # token: eyJhbGciOi... + + # Shell script that prints a bearer token on stdout. Recommended when + # the auth provider's token semantics do not match nicocli's built-in + # OIDC grants. + # token_command: /home/me/.nico/get-nico-token.sh + + # OIDC password / client-credentials grant (Keycloak and similar). + # oidc: + # token_url: https://keycloak.example.com/realms/nico-dev/protocol/openid-connect/token + # client_id: nico-api + # client_secret: + + # NGC API key. nvapi- prefixed keys are bearer tokens directly; + # legacy keys must specify authn_url for exchange. + # api_key: + # key: nvapi-xxxx + # # authn_url: https://your-authn-server/token # only for legacy keys +``` + +`nicocli init` writes a fully commented template -- start from that rather than copying the snippet above by hand. + +## Authentication + +`nicocli login` runs the appropriate auth flow and saves the resulting bearer token back to the active config. Pick the method that matches your deployment. + +### Auth method selection order + +When you run `nicocli login`, nicocli decides which flow to use in this order: + +1. `--token-command` flag (overrides everything; persists `auth.token_command` to config). +2. Explicit `--api-key` / `--authn-url` flags. +3. Explicit `--token-url` / `--client-secret` / `--username` / `--password` / `--keycloak-url` flags. +4. `auth.token_command` in config. +5. `auth.oidc` in config. +6. `auth.api_key` in config. +7. Default OIDC password grant (fails if no token URL is configured). + +If your config is set up correctly, plain `nicocli login` picks the right method. The flag forms below are mostly useful for first-time setup and CI scripts. + +### Static bearer token + +For short interactive sessions or when token issuance is handled outside nicocli: + +```yaml +auth: + token: eyJhbGciOi... +``` + +No `nicocli login` is needed -- the token is used directly. When it expires, paste a fresh one or move to `token_command`. + +### Token command (external auth scripts) + +When your auth provider's token semantics do not match nicocli's built-in OIDC grants -- for example, when the provider requires a custom OAuth scope, an mTLS-fronted token endpoint, or a wrapper that fetches credentials from a secret store -- write a small script that prints a bearer token to stdout and reference it from config: + +```yaml +auth: + token_command: /home/me/.nico/get-nico-token.sh +``` + +Or for a one-shot login that also persists `token_command` to the config: + +``` +nicocli --config ~/.nico/config.staging.yaml \ + --token-command ~/.nico/get-nico-token.sh \ + login +``` + +After login, every subsequent nicocli command re-runs the script when the cached token is near expiry. The bearer token is redacted in `--debug` output, but the path to the script is visible in the config. + +The script must: + +- Print exactly one line: the bearer token. No log noise on stdout. +- Exit zero on success, non-zero on failure. +- Be reasonably fast (re-run on token expiry, so seconds matter). + +Minimal example -- adapt the curl payload and headers to whatever your provider requires: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# Source credentials however your deployment manages them (env vars, +# secret store CLI, file with 0600 perms, etc.). This example assumes +# the client_id and client_secret are already exported. + +curl -sS \ + -u "${CLIENT_ID}:${CLIENT_SECRET}" \ + -d grant_type=client_credentials \ + -d scope="${OAUTH_SCOPE:-openid}" \ + "${TOKEN_URL}" \ + | jq -r '.access_token' +``` + +`chmod 700` the script -- it's invoked on every token refresh. + +### OIDC password / client-credentials (Keycloak) + +For deployments backed by Keycloak (or any OIDC IdP that supports the standard password or client-credentials grants), put the token URL and client identity in config: + +```yaml +auth: + oidc: + token_url: https://keycloak.example.com/realms/nico-dev/protocol/openid-connect/token + client_id: nico-api + client_secret: +``` + +Then: + +``` +nicocli login # password grant; prompts for user/pass +nicocli login --username alex@example.com # supply username, prompted for password +nicocli login --client-secret "$NICO_CLIENT_SECRET" # client-credentials grant +``` + +Keycloak shorthand (constructs the token URL automatically as `/realms//protocol/openid-connect/token`): + +``` +nicocli --keycloak-url https://keycloak.example.com \ + login --username alex@example.com +``` + +The default realm is `nico-dev`; override with `--keycloak-realm` or `NICO_KEYCLOAK_REALM`. + +After login, the access token, refresh token, and expiry time are saved back to the config. The TUI auto-refreshes tokens 30 seconds before expiry and retries failed requests on `401 Unauthorized` up to three times. + +> **OIDC scope is hardcoded**: the built-in password and client-credentials grants both send `scope=openid` -- this is not configurable. If your IdP requires a different scope, use `auth.token_command` instead. + +### NGC API key + +For NGC-backed deployments: + +```yaml +auth: + api_key: + key: nvapi-xxxx +``` + +Keys prefixed with `nvapi-` are bearer tokens directly -- no `authn_url` is required. Legacy NGC API keys without the prefix must be exchanged via the `authn_url`: + +```yaml +auth: + api_key: + key: + authn_url: https://your-authn-server/token +``` + +The CLI errors clearly if `authn_url` is missing on a legacy key. `nvapi-` keys never need it. + +### Environment variables + +Every auth flag has a corresponding env var, useful for CI/CD pipelines: + +| Flag | Env var | +|------|---------| +| `--config` | `NICO_CONFIG` | +| `--base-url` | `NICO_BASE_URL` | +| `--org` | `NICO_ORG` | +| `--token` | `NICO_TOKEN` | +| `--token-command` (alias `--auth-script`) | `NICO_TOKEN_COMMAND` (alias `NICO_AUTH_SCRIPT`) | +| `--token-url` | `NICO_TOKEN_URL` | +| `--keycloak-url` | `NICO_KEYCLOAK_URL` | +| `--keycloak-realm` | `NICO_KEYCLOAK_REALM` | +| `--client-id` | `NICO_CLIENT_ID` | +| `--client-secret` | `NICO_CLIENT_SECRET` | +| `--api-key` | `NICO_API_KEY` | +| `--authn-url` | `NICO_AUTHN_URL` | + +### Verifying authentication + +After login, confirm nicocli can reach the API and your identity is correct: + +``` +nicocli site list # any list returns -> auth works +nicocli user get # returns the caller's user record +nicocli service-account get # for service-account deployments only +``` + +`user get` returns the authenticated identity as NICo sees it. Service accounts have blank `email`/`firstName`/`lastName`; human users have those populated from the IdP. + +## Command Structure + +### Resources and actions + +nicocli commands are generated from the OpenAPI spec. Each tag becomes a top-level resource; each operation becomes an action under it: + +``` +nicocli [flags...] [positional args] +``` + +Examples: + +``` +nicocli site list +nicocli allocation get +nicocli instance update --trigger-reboot=true +``` + +Some resources have sub-resources -- a third grouping level. Constraints under allocations are a typical case: + +``` +nicocli allocation constraint update --constraint-value 12 +``` + +Run `nicocli --help` to enumerate available actions and sub-resources for any tag. + +### Action name resolution + +The CLI's action-name resolver collapses `get-current-X` operation IDs to a short `get` action when there's no sibling collision. When two operation IDs would collide on the short form, both keep their full names. + +| Operation ID | Resource | Action name | +|--------------|----------|-------------| +| `get-current-user` | `user` | `get` (no collision) | +| `get-current-service-account` | `service-account` | `get` (no collision) | +| `get-current-tenant` | `tenant` | `get-current-tenant` (collision with `get-current-tenant-stats`) | +| `get-current-tenant-stats` | `tenant` | `get-current-tenant-stats` (collision) | + +Use `--help` to confirm the actual action name for any resource if you're unsure. + +### Flag ordering + +Flags MUST come before positional arguments. nicocli uses urfave/cli, which stops parsing flags at the first positional. Examples: + +``` +# correct +nicocli instance update --trigger-reboot=true +nicocli allocation constraint update --constraint-value 12 +nicocli tenant-account update --data '{}' + +# wrong -- flags after positionals are rejected +nicocli instance update --trigger-reboot=true +``` + +When the ordering is wrong, the CLI prints a clear error: + +``` +Error: flag(s) --data placed after a positional argument; urfave/cli (stdlib flag) +stops parsing flags at the first positional, so these flags are being ignored. +Move all flags before positionals, e.g. + nicocli tenant-account update [flags...] +``` + +### `--data` vs flag forms + +Most create and update operations expose every body field as an individual flag. Prefer the flag form -- it's shorter and gets validated up front. For example: + +``` +nicocli instance update --trigger-reboot=true +nicocli instance update --name acme-worker-01-renamed +nicocli vpc create --name acme-prod --site-id --routing-profile internal +``` + +Use `--data ''` or `--data-file ` (use `-` for stdin) only when: + +- A field is array-typed. `interfaces[]`, `sshKeyGroupIds[]`, `allocationConstraints[]`, `nvLinkInterfaces[]`, and similar arrays cannot be set through individual flags -- they go through the body. +- You want to round-trip an entire resource through `get -o json` -> edit -> `update --data-file`. + +JSON bodies use camelCase (`siteId`, `vpcId`, `ipv4BlockId`) -- the same as the REST API request bodies. Flag names are kebab-cased mechanically, which is not always invertible. Two gotchas: + +- Digit-to-letter transitions get no separator: `ipv4BlockId` -> `--ipv4block-id`, not `--ipv4-block-id`. +- Acronyms preserve their grouping: `vniId` -> `--vni-id`; `nvLinkLogicalPartitionId` -> `--nv-link-logical-partition-id`. + +When in doubt, run ` --help` to see the exact flag name. + +## Output Formatting + +### `--output` + +List and get commands support `--output `: + +| Format | Use | +|--------|-----| +| `json` (default) | Structured output, suitable for `jq` | +| `yaml` | Structured output, easier to read by hand | +| `table` | Minimal columns. Some endpoints (notably `audit list`) only show `id` -- use `json` for full detail. | + +``` +nicocli site list --output table +nicocli tenant get-current-tenant --output yaml +nicocli audit list --output json --page-size 50 | jq '.[] | select(.statusCode >= 400)' +``` + +The detail (`get `) view is always richer than the list view -- list endpoints often omit nested objects for performance. If `list` doesn't show a field you need, `get` likely will. + +### Pagination + +List commands accept `--page-size`, `--page-number`, and `--all`: + +``` +nicocli audit list --page-size 50 --page-number 2 +nicocli allocation list --all +``` + +When results span multiple pages, a one-line pagination summary is printed to **stderr** above the data: + +``` +Page 1/18 (5 items, 88 total). Use --all to fetch everything. +``` + +When piping to `jq`, suppress the summary with `2>/dev/null`: + +``` +nicocli audit list --output json --page-size 50 2>/dev/null \ + | jq '.[] | {id, endpoint, method, statusCode}' +``` + +`--all` follows pagination links automatically (capped at 1000 pages). For very large result sets, paginate explicitly to stay within memory budgets. + +### Filter flags vs `--query` + +List endpoints expose dedicated filter flags derived from the OpenAPI spec. For example, `nicocli allocation list --help` shows `--site-id`, `--tenant-id`, `--resource-type`, `--resource-type-id`, `--status`, `--constraint-type`, `--constraint-value`, `--include-relation`, `--order-by`, plus the standard pagination flags. Use those. + +The `--query` flag is a **free-text search across `name`, `description`, and `status` fields** -- it is NOT a key=value filter. `--query "resourceType=InstanceType"` matches the literal substring `resourceType=InstanceType` in those fields, which is almost never what you want. Reach for the dedicated flags. + +## Debugging + +### `--debug` + +The global `--debug` flag logs the full HTTP request and response for the wrapped command. The bearer token is redacted; everything else is visible: + +``` +$ nicocli --debug tenant get-current-tenant +time=... msg="API request: GET https://nico.example.com/v2/org/my-org/nico/tenant/current" +time=... msg="Request headers: {\"Accept\":[\"application/json\"],\"Authorization\":[\"Bearer \"]}" +time=... msg="API response: ... -> 200 OK" +time=... msg="Response body: {...}" +``` + +Use it for: + +- Confirming the rewritten URL has the right `api.name` segment. +- Seeing the exact body the server received (handy for `--data` debugging). +- Capturing the full failure payload when a command returns a non-2xx response. + +### `api.name` rewriting + +nicocli substitutes the API name segment in every URL before sending. The OpenAPI spec uses `nico` as the placeholder; the deployment may use any value its operators chose. nicocli rewrites `/v2/org//nico/...` to `/v2/org///...` transparently. `--debug` shows the rewritten URL, which is what you should see in the API server's access log. + +### Version mismatch is normal + +``` +nicocli --version +``` + +The CLI is generated from the OpenAPI spec at build time; the server reports its own image version (visible as `apiVersion` on audit responses). The two version schemes are independent -- mismatches are normal and rarely matter, as the wire protocol is stable across patch and minor releases. + +## TUI Mode + +For exploratory work and one-off operations, the TUI is the recommended interface: + +``` +nicocli tui # full command +nicocli i # alias +``` + +Behavior: + +- Discovers every `config*.yaml` in `~/.nico/` and presents them as a selection list at startup. +- Authenticates using the chosen config's auth method (token, token-command, OIDC, or API key). +- Drops into an interactive prompt with tab completion across all generated commands. +- Scopes lookups appropriately -- for example, `allocation create` only shows instance types and IP blocks that belong to the selected site, which avoids the common mistake of cross-site allocations. +- Auto-refreshes tokens 30 seconds before expiry and retries failed requests on `401 Unauthorized` up to three times. + +The TUI's interactive forms for `create` commands prompt for fields in order with type-aware pickers. For first-time operators this is significantly easier than constructing JSON bodies by hand. For scripts and automation, fall back to the non-interactive command form. + +## Quick Reference + +| Operation | Command | +|-----------|---------| +| Generate sample config | `nicocli init` | +| Authenticate (config-driven) | `nicocli login` | +| Authenticate (token-command flag) | `nicocli --token-command login` | +| Authenticate (API key flag) | `nicocli login --api-key nvapi-xxxx` | +| Authenticate (Keycloak shorthand) | `nicocli --keycloak-url login --username ` | +| Verify identity | `nicocli user get` | +| Pick environment interactively | `nicocli tui` | +| Pick environment non-interactively | `nicocli --config ~/.nico/config..yaml ...` | +| List with table output | `nicocli list --output table` | +| Fetch every page of a list | `nicocli list --all` | +| Inspect HTTP exchange | `nicocli --debug ` | +| Print CLI version | `nicocli --version` | + +## Related Documentation + +- [Quick Start Guide](../getting-started/quick-start.md) -- NICo deployment and Day Zero walkthrough; covers the bundled Keycloak setup. +- [Day One Operations](day_one_operations.md) -- Operator workflow for tenant management, allocations, VPCs, subnets, and instance provisioning. Uses this reference for all CLI mechanics. +- [Reference Installation](../getting-started/installation-options/reference-install.md) -- Deployment configuration surface, including the `issuers` block that maps OIDC IdPs to NICo.