# 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:` and `netscaler-keypass:`. - **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": "", "keyalgorithm": "RSA", "keystrength": "4096" } } ``` - `config_command` payload shapes tried in `execute_on_device`: ```json {"config_command": {"device_id": "", "command": ""}} {"config_command": {"device_id": "", "commands": ["cmd1","cmd2"]}} {"config_command": {"instance_id": "", "command": "..."}} ``` - `ns_command` fallback shapes: ```json {"ns_command": {"device_id": "", "commands": ["cmd1"]}} {"ns_command": {"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.