276 lines
9.6 KiB
Markdown
276 lines
9.6 KiB
Markdown
# Copilot / Agent Instructions
|
||
|
||
Purpose: Help an AI coding agent become productive quickly in this small repo.
|
||
|
||
- **Project type**: Single-file Python CLI tool (`nscertkeycreate.py`). It interacts with a Citrix NetScaler (Console) Nitro API and optionally executes ADC CLI on managed devices.
|
||
|
||
- **Big picture**: There are two main flows:
|
||
- **Console-only CSR**: `NitroClient.create_csr_console_side` posts to `ns_ssl_csr` and saves CSR locally.
|
||
- **Deliver-to-device**: `NitroClient.execute_on_device` tries Console-side command execution (preferred: `config_command` / `config_job`) and falls back to `ns_command`. Device-side commands are generated by `adc_cli_commands_for_key_and_csr` and `adc_cli_command_to_cat_csr`.
|
||
|
||
- **Key integration points**:
|
||
- macOS Keychain via `security` CLI — helpers: `keychain_get`, `keychain_set`, `keychain_delete`.
|
||
- HTTP: uses `urllib.request` with Basic auth; base URLs built from `--console` into `/nitro/v1/config` and `/nitro/v2/config`.
|
||
- ADC command execution: multiple payload shapes are tried (see `payloads_config_command` and `payloads_ns_command`). To support a new Console schema, add payload shapes here.
|
||
- Task polling: `try_poll_task_logs` performs best-effort polling of `task_log` and `task_command_log`.
|
||
|
||
- **Error & observability patterns**:
|
||
- Failures raise `NitroError` containing the raw payload returned by the server; callers typically `json.dumps` that payload and print it.
|
||
- When adding parsing for device command output, prefer dumping the raw JSON first (the code writes `*.device_cat_csr.response.json`) and then implement a deterministic extractor.
|
||
|
||
- **Developer workflows & quick commands**:
|
||
- Run interactively (examples live in the CLI epilog):
|
||
|
||
- Console-only CSR:
|
||
|
||
./nscertkeycreate.py --console https://CONSOLE --user nsroot --name app1
|
||
|
||
- Deliver to Primary device and fetch CSR back:
|
||
|
||
./nscertkeycreate.py --console https://CONSOLE --user nsroot --name app1 --deliver-to-device --download-csr-from-device
|
||
|
||
- On macOS, inspect keychain entries with the `security` tool; the script stores entries under service names like `netscaler-console:<console>` and `netscaler-keypass:<console>`.
|
||
|
||
- **Where to make common changes**:
|
||
- Add/adjust Nitro payload shapes: edit `payloads_config_command` / `payloads_ns_command` inside `NitroClient.execute_on_device`.
|
||
- Change how CSR text is extracted from Console responses: modify `extract_csr_text`.
|
||
- Adapt device-response parsing (stdout extraction) after `execute_on_device`: check where `*.device_cat_csr.response.json` is written and add a deterministic parser.
|
||
|
||
- **Automation notes**:
|
||
- The CLI is interactive by default (prompts for password, passphrase, subject fields). For automation, pre-seed macOS Keychain entries so the script won’t prompt, and pass flags like `--outdir` and `--timeout` on the command line.
|
||
|
||
- **Files to inspect for context**:
|
||
- [nscertkeycreate.py](nscertkeycreate.py)
|
||
- [README.md](README.md)
|
||
|
||
---
|
||
|
||
## Concrete payload examples
|
||
|
||
- Console CSR POST (used by `create_csr_console_side`):
|
||
|
||
```json
|
||
{
|
||
"ns_ssl_csr": {
|
||
"file_name": "app1.csr",
|
||
"commonname": "app.example.com",
|
||
"organizationname": "Example Ltd",
|
||
"countryname": "US",
|
||
"statename": "California",
|
||
"passphrase": "<key-pass>",
|
||
"keyalgorithm": "RSA",
|
||
"keystrength": "4096"
|
||
}
|
||
}
|
||
```
|
||
|
||
- `config_command` payload shapes tried in `execute_on_device`:
|
||
|
||
```json
|
||
{"config_command": {"device_id": "<id>", "command": "<newline-separated commands>"}}
|
||
{"config_command": {"device_id": "<id>", "commands": ["cmd1","cmd2"]}}
|
||
{"config_command": {"instance_id": "<id>", "command": "..."}}
|
||
```
|
||
|
||
- `ns_command` fallback shapes:
|
||
|
||
```json
|
||
{"ns_command": {"device_id": "<id>", "commands": ["cmd1"]}}
|
||
{"ns_command": {"id": "<id>", "command": "cmd1\ncmd2"}}
|
||
```
|
||
|
||
When Console responses fail, the script aggregates attempts into `NitroError.payload`. Use that dumped JSON to see which schema matched and where stdout may live.
|
||
|
||
## Targeted code references
|
||
|
||
- `create_csr_console_side` — [nscertkeycreate.py](nscertkeycreate.py#L160)
|
||
- `execute_on_device` — [nscertkeycreate.py](nscertkeycreate.py#L177)
|
||
- `adc_cli_commands_for_key_and_csr` — [nscertkeycreate.py](nscertkeycreate.py#L319)
|
||
- `adc_cli_command_to_cat_csr` — [nscertkeycreate.py](nscertkeycreate.py#L374)
|
||
- `extract_csr_text` — [nscertkeycreate.py](nscertkeycreate.py#L299)
|
||
|
||
Use these links when you need to change payload shapes, CSR extraction, or device-command parsing.
|
||
|
||
---
|
||
|
||
## Sample Console responses
|
||
|
||
Below are common response shapes you may encounter. When in doubt, dump the full JSON (the script writes `*.device_cat_csr.response.json`) and adapt an extractor that searches string fields for the PEM block.
|
||
|
||
- Successful `ns_ssl_csr` response that returns CSR text (how `extract_csr_text` expects it):
|
||
|
||
```json
|
||
{
|
||
"ns_ssl_csr": [
|
||
{
|
||
"file_name": "app1.csr",
|
||
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...\n-----END CERTIFICATE REQUEST-----",
|
||
"errorcode": 0
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
```
|
||
|
||
- Typical `config_command` / `config_job` success payloads (IDs/tokens are returned under several keys):
|
||
|
||
```json
|
||
{ "config_command": { "id": "cc-1001", "message": "Accepted", "errorcode": 0 } }
|
||
{ "config_command": { "job_id": "job-42", "message": "Queued", "errorcode": 0 } }
|
||
{ "task_id": "task-900", "message": "Submitted", "errorcode": 0 }
|
||
```
|
||
|
||
- `ns_command` responses sometimes return per-command stdout/rows. Look for `stdout`, `response`, or nested arrays:
|
||
|
||
```json
|
||
{
|
||
"ns_command": {
|
||
"id": "nc-2001",
|
||
"commands": [
|
||
{
|
||
"command": "shell cat /nsconfig/ssl/app1.csr",
|
||
"stdout": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...\n-----END CERTIFICATE REQUEST-----"
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
- `task_log` / `task_command_log` entries (polled by `try_poll_task_logs`) may contain tokens referencing the originating ID:
|
||
|
||
```json
|
||
{
|
||
"task_log": [
|
||
{ "id": "t-1", "message": "Executed config_command id=cc-1001", "errorcode": 0 }
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
```
|
||
|
||
Parsing guidance:
|
||
- To wire automatic CSR extraction from device responses, first save the raw JSON (the script already does this), then implement a deterministic extractor that:
|
||
- searches for PEM blocks (regex for -----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----) across any string fields, and
|
||
- falls back to looking at `ns_command` -> `commands`[*] -> `stdout` or `response` fields.
|
||
- When `NitroError.payload` contains `attempts`, inspect each attempt's `url` and `payload` to determine which schema succeeded or returned useful stdout.
|
||
|
||
---
|
||
|
||
## Real-world Console response examples
|
||
|
||
The snippets below are realistic, slightly-expanded examples you may see when interacting with NetScaler Consoles. Use them to test extractors and to map where stdout or CSR text appears.
|
||
|
||
- Example A — `ns_ssl_csr` returns CSR in `csr` field (successful Console-side CSR creation):
|
||
|
||
```json
|
||
{
|
||
"ns_ssl_csr": [
|
||
{
|
||
"file_name": "app1.csr",
|
||
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICWjCCA...\n-----END CERTIFICATE REQUEST-----",
|
||
"errorcode": 0
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
```
|
||
|
||
- Example B — `config_command` accepted and queued (returns id/job_id/task_id in different builds):
|
||
|
||
```json
|
||
{
|
||
"config_command": {
|
||
"id": "cc-1001",
|
||
"message": "Accepted",
|
||
"errorcode": 0
|
||
}
|
||
}
|
||
|
||
{
|
||
"config_command": {
|
||
"job_id": "job-42",
|
||
"message": "Queued",
|
||
"errorcode": 0
|
||
}
|
||
}
|
||
|
||
{
|
||
"task_id": "task-900",
|
||
"message": "Submitted",
|
||
"errorcode": 0
|
||
}
|
||
```
|
||
|
||
- Example C — `ns_command` with per-command `stdout` (device-side `shell cat` returns CSR text here):
|
||
|
||
```json
|
||
{
|
||
"ns_command": {
|
||
"id": "nc-2001",
|
||
"commands": [
|
||
{
|
||
"command": "shell cat /nsconfig/ssl/app1.csr",
|
||
"stdout": "-----BEGIN CERTIFICATE REQUEST-----\nMIICWjCCA...\n-----END CERTIFICATE REQUEST-----",
|
||
"response": null
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
- Example D — `ns_command` returns nested arrays or `rows` (some Console builds):
|
||
|
||
```json
|
||
{
|
||
"ns_command": {
|
||
"id": "nc-2002",
|
||
"commands": [
|
||
{
|
||
"command": "shell cat /nsconfig/ssl/app1.csr",
|
||
"rows": [
|
||
"-----BEGIN CERTIFICATE REQUEST-----",
|
||
"MIICWjCCA...",
|
||
"-----END CERTIFICATE REQUEST-----"
|
||
]
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
- Example E — `task_log` entry referencing a config_command/job (polled by `try_poll_task_logs`):
|
||
|
||
```json
|
||
{
|
||
"task_log": [
|
||
{
|
||
"id": "t-1",
|
||
"message": "Executed config_command id=cc-1001 on device id=dev-5",
|
||
"errorcode": 0
|
||
}
|
||
],
|
||
"errorcode": 0
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
Testing tips:
|
||
- Save any device response JSON produced by the script (e.g., `out/app1.device_cat_csr.response.json`) and run a quick search for `-----BEGIN CERTIFICATE REQUEST-----` to locate the PEM.
|
||
- A robust extractor should:
|
||
- recursively walk the JSON and scan every string value for a PEM regex, returning the first match; and
|
||
- if no PEM is found, inspect `ns_command` -> `commands`[*] for `stdout`, `response`, `rows`, etc.
|
||
|
||
Small regex to find PEM blocks (Python):
|
||
|
||
```python
|
||
import re
|
||
PEM_RE = re.compile(r"-----BEGIN CERTIFICATE REQUEST-----(?:.|\n)+?-----END CERTIFICATE REQUEST-----")
|
||
```
|
||
|
||
If you want, I can implement a small `extract_pem_from_json(resp: dict) -> Optional[str]` helper in `nscertkeycreate.py` and wire it to save the CSR automatically when `--download-csr-from-device` is used. Paste one or two real Console JSON dumps if you want the extractor tailored to your Console's exact schema.
|
||
|
||
If anything here is unclear or you want more examples (e.g., sample payload shapes from a real Console), tell me which area to expand and I’ll update this file.
|