9.6 KiB
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_sideposts tons_ssl_csrand saves CSR locally. - Deliver-to-device:
NitroClient.execute_on_devicetries Console-side command execution (preferred:config_command/config_job) and falls back tons_command. Device-side commands are generated byadc_cli_commands_for_key_and_csrandadc_cli_command_to_cat_csr.
- Console-only CSR:
-
Key integration points:
- macOS Keychain via
securityCLI — helpers:keychain_get,keychain_set,keychain_delete. - HTTP: uses
urllib.requestwith Basic auth; base URLs built from--consoleinto/nitro/v1/configand/nitro/v2/config. - ADC command execution: multiple payload shapes are tried (see
payloads_config_commandandpayloads_ns_command). To support a new Console schema, add payload shapes here. - Task polling:
try_poll_task_logsperforms best-effort polling oftask_logandtask_command_log.
- macOS Keychain via
-
Error & observability patterns:
- Failures raise
NitroErrorcontaining the raw payload returned by the server; callers typicallyjson.dumpsthat 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.
- Failures raise
-
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
securitytool; the script stores entries under service names likenetscaler-console:<console>andnetscaler-keypass:<console>.
-
-
Where to make common changes:
- Add/adjust Nitro payload shapes: edit
payloads_config_command/payloads_ns_commandinsideNitroClient.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.jsonis written and add a deterministic parser.
- Add/adjust Nitro payload shapes: edit
-
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
--outdirand--timeouton the command line.
- 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
-
Files to inspect for context:
Concrete payload examples
- Console CSR POST (used by
create_csr_console_side):
{
"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_commandpayload shapes tried inexecute_on_device:
{"config_command": {"device_id": "<id>", "command": "<newline-separated commands>"}}
{"config_command": {"device_id": "<id>", "commands": ["cmd1","cmd2"]}}
{"config_command": {"instance_id": "<id>", "command": "..."}}
ns_commandfallback shapes:
{"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.pyexecute_on_device— nscertkeycreate.pyadc_cli_commands_for_key_and_csr— nscertkeycreate.pyadc_cli_command_to_cat_csr— nscertkeycreate.pyextract_csr_text— nscertkeycreate.py
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_csrresponse that returns CSR text (howextract_csr_textexpects it):
{
"ns_ssl_csr": [
{
"file_name": "app1.csr",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...\n-----END CERTIFICATE REQUEST-----",
"errorcode": 0
}
],
"errorcode": 0
}
- Typical
config_command/config_jobsuccess payloads (IDs/tokens are returned under several keys):
{ "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_commandresponses sometimes return per-command stdout/rows. Look forstdout,response, or nested arrays:
{
"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_logentries (polled bytry_poll_task_logs) may contain tokens referencing the originating ID:
{
"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[*] ->stdoutorresponsefields.
- When
NitroError.payloadcontainsattempts, inspect each attempt'surlandpayloadto 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_csrreturns CSR incsrfield (successful Console-side CSR creation):
{
"ns_ssl_csr": [
{
"file_name": "app1.csr",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICWjCCA...\n-----END CERTIFICATE REQUEST-----",
"errorcode": 0
}
],
"errorcode": 0
}
- Example B —
config_commandaccepted and queued (returns id/job_id/task_id in different builds):
{
"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_commandwith per-commandstdout(device-sideshell catreturns CSR text here):
{
"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_commandreturns nested arrays orrows(some Console builds):
{
"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_logentry referencing a config_command/job (polled bytry_poll_task_logs):
{
"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[*] forstdout,response,rows, etc.
Small regex to find PEM blocks (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.