Skip to content

shigechika/junos-ops

Repository files navigation

junos-ops

PyPI version CI Python

日本語版 / Japanese

A Python CLI to automate Juniper/JUNOS operations over NETCONF: model-aware upgrade, rollback, reboot, config push, and RSI/SCF collection.

RSI/SCF? RSI = request support information (the JTAC-style support snapshot), SCF = show configuration | display set. Collected files are saved with .rsi / .scf extensions — hence the shorthand used throughout this tool.

Features

  • Automatic device model detection and package mapping
  • Safe package copy via SCP with checksum verification
  • Pre-install package validation
  • Rollback support (model-specific handling for MX/EX/SRX)
  • Scheduled reboot with automatic config-drift detection and re-install
  • On-demand recovery snapshot (snapshot) to sync the alternate boot media (MX-focused), with an alternate-media safety guard
  • Parallel RSI (request support information) / SCF (show configuration | display set) collection
  • Pre-flight check subcommand: NETCONF reachability, local firmware checksum (device-less), and remote firmware checksum in one unified table
  • Arbitrary CLI command execution across hosts (show) with RpcTimeoutError retry
  • Configuration push with commit confirmed safety and post-commit health checks (ping, uptime NETCONF probe, or any CLI command)
  • Jinja2 template support for per-host configuration generation (details)
  • Tag-based host filtering (--tags) for AND-matched multi-site workflows
  • Local firmware directory (lpath) with ~ expansion
  • Dry-run mode (--dry-run) for pre-flight verification
  • Machine-readable JSON output (--json) for piping into jq / monitoring / Ansible
  • Parallel execution via ThreadPoolExecutor (--workers N)
  • INI-based host and package management

Table of Contents

Installation

Homebrew (macOS)

brew install shigechika/tap/junos-ops

Debian / Ubuntu (.deb)

Pre-built packages for Ubuntu 24.04 (Noble) are available on the GitHub Releases page. The package installs a self-contained Python virtualenv at /opt/venvs/junos-ops/ with junos-ops symlinked to /usr/bin/:

sudo apt install ./junos-ops_*~noble.deb

RHEL / Rocky Linux / AlmaLinux (.rpm)

Pre-built packages for RHEL/Rocky/AlmaLinux 9 are available on the GitHub Releases page. Python 3.12 is required:

sudo dnf install python3.12
sudo rpm -ivh junos-ops-*-1.el9.x86_64.rpm

pip

pip install junos-ops

To upgrade to the latest version:

pip install junos-ops --upgrade

Development Setup

git clone https://github.com/shigechika/junos-ops.git
cd junos-ops
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[test]"

Dependencies

Tab Completion (optional)

pip install junos-ops[completion]
eval "$(register-python-argcomplete junos-ops)"

Add the eval line to your shell profile (~/.bashrc or ~/.zshrc) to enable it permanently.

Installing pip3 (if not available)

OS-specific instructions
  • Ubuntu/Debian

    sudo apt install python3-pip
  • CentOS/RedHat

    sudo dnf install python3-pip
  • macOS

    brew install python3

Configuration File (config.ini)

An INI-format configuration file that defines connection settings and model-to-package mappings.

The configuration file is searched in the following order (-c / --config can override):

  1. ./config.ini in the current directory
  2. ~/.config/junos-ops/config.ini (XDG_CONFIG_HOME)

Logging Configuration (logging.ini)

An optional logging.ini file can be used to customize log output (e.g., suppress verbose paramiko/ncclient messages). The file is searched in the same order as config.ini:

  1. ./logging.ini in the current directory
  2. ~/.config/junos-ops/logging.ini (XDG_CONFIG_HOME)

If neither is found, the default logging configuration (INFO level to stdout) is used.

DEFAULT Section

Defines global connection settings and model-to-package mappings shared by all hosts.

[DEFAULT]
id = exadmin          # SSH username
pw = password         # SSH password
sshkey = id_ed25519   # SSH private key file
port = 830            # NETCONF port
hashalgo = md5        # Checksum algorithm
rpath = /var/tmp      # Remote path
# ssh_config = ~/.ssh/config   # OpenSSH config (ProxyCommand etc.); if unset, PyEZ auto-picks up ~/.ssh/config
# lpath = ~/firmware   # Local firmware directory (~ expanded, default: current directory)
# huge_tree = true    # Allow large XML responses
# RSI_DIR = ./rsi/    # Output directory for RSI/SCF files
# DISPLAY_STYLE = display set   # SCF output style (default: display set)
# DISPLAY_STYLE =               # Empty for stanza format (show configuration only)

# model.file = package filename
# model.hash = checksum value
EX2300-24T.file = junos-arm-32-18.4R3-S10.tgz
EX2300-24T.hash = e233b31a0b9233bc4c56e89954839a8a

The model name must match the model field automatically retrieved from the device.

Host Sections

Each section name becomes the hostname. DEFAULT values can be overridden per host.

[rt1.example.jp]             # Section name is used as the connection hostname
tags = tokyo, core           # Tag-based host filtering (--tags)
[rt2.example.jp]
host = 192.0.2.1             # Override connection target with IP address
tags = osaka, core
[sw1.example.jp]
id = sw1                     # Override SSH username
sshkey = sw1_rsa             # Override SSH key
ssh_config = ~/.ssh/config.lab   # Per-host OpenSSH config (e.g. behind a bastion)
tags = tokyo, access
[sw2.example.jp]
port = 10830                 # Override port
[sw3.example.jp]
EX4300-32F.file = jinstall-ex-4300-20.4R3.8-signed.tgz   # Different version for this host
EX4300-32F.hash = 353a0dbd8ff6a088a593ec246f8de4f4

Usage

junos-ops <subcommand> [options] [hostname ...]

Subcommands

Subcommand Description
upgrade [--unlink] Copy and install package (--unlink for low-flash devices, see below)
copy Copy package from local to remote
install [--unlink] Install a previously copied package (--unlink for low-flash devices, see below)
rollback Rollback to the previous version
version Show running/planning/pending versions and reboot schedule
reboot --at YYMMDDHHMM Schedule a reboot at the specified time
snapshot [--force] Create a recovery snapshot (request system snapshot) to sync the alternate boot media; MX-focused. Refuses if the device is running on its alternate media unless --force. See snapshot below
ls [-l] List files on the remote path
show COMMAND [--retry N] / show -f FILE Run an arbitrary CLI command (or file of commands) across devices
check [--connect|--local|--remote|--all] [--model M] Pre-flight checks: NETCONF reachability, local/remote firmware checksum
config -f FILE Push a set command file (see docs/config.md for --confirm, --timeout, --no-confirm, --no-commit, --health-check, --no-health-check)
rsi Collect RSI/SCF in parallel
(none) Show device facts

Common Options

Option Description
hostname Target hostname(s) (defaults to all hosts in config file)
-c, --config CONFIG Config file path (default: config.ini or ~/.config/junos-ops/config.ini)
-n, --dry-run Test run (connect and display messages only, no execution)
-d, --debug Debug output
--force Force execution regardless of conditions
--json Emit machine-readable JSON instead of human-readable text. One JSON object per host per line (JSONL); logs are routed to stderr so stdout stays pure JSON. Pipe to jq -s to slurp into an array. See "JSON Output" below.
--tags TAG[,TAG...] Filter hosts by tags. Comma-separates AND together inside one value; repeating --tags ORs groups. Combined with explicit hostnames, the tag filter and hostname list intersect. See "Tag-based Host Filtering" below.
--exclude-tags TAG[,TAG...] Drop hosts whose tags match. Same AND/OR grammar as --tags (comma = AND within a group, repeat to OR groups). Applied after --tags; usable standalone to subtract a subset from the default "all hosts" selection.
--workers N Parallel workers (default: 1 for upgrade, 20 for rsi)
--version Show program version

JSON Output

Pass --json to any subcommand to get machine-readable output. Each host emits one JSON object on its own line (JSON Lines / JSONL), which composes cleanly with parallel --workers N runs:

$ junos-ops version --json rt1.example.jp rt2.example.jp
{"hostname": "rt1.example.jp", "ok": true, "model": "MX240", "running": "22.4R3-S6.5", ...}
{"hostname": "rt2.example.jp", "ok": true, "model": "EX2300-24T", "running": "22.4R3-S6.5", ...}
  • Logs (including config's real-time commit progress) and startup diagnostics (unreadable config, no matching hosts) go to stderr, so 2>/dev/null or piping stdout alone yields valid JSON only. Check the exit code to detect a startup failure (stdout is then empty).
  • A host that fails to connect or errors mid-run still emits a line — {"hostname": ..., "ok": false, "error": ..., "error_message": ...} — so a consumer never silently misses a host.
  • Slurp the stream into a single array with jq -s: junos-ops version --json | jq -s '.'
  • check --json tags each line with "check": "local" (model-keyed inventory rows) or "check": "host" (per-host rows).

Workflow

CLI Architecture Overview

All subcommands share the same execution pipeline: read the config file, determine target hosts (optionally filtered by --tags), then dispatch each host to a worker thread via ThreadPoolExecutor. The --workers N option controls parallelism — defaulting to 1 for upgrade operations (safe sequential execution) and 20 for RSI collection (I/O-bound, benefits from concurrency). Each worker establishes its own NETCONF session, so hosts are processed independently with no shared state.

flowchart TD
    A[junos-ops CLI] --> B[Read config.ini]
    B --> C[Determine target hosts]
    C --> D{Subcommand}
    D --> E[upgrade / copy / install]
    D --> F[version / rollback / reboot]
    D --> G[config / show / ls]
    D --> H[rsi]
    D --> I["(none) → facts"]
    E & F & G & H & I --> J["ThreadPoolExecutor<br/>--workers N"]
    J --> K["NETCONF / SCP<br/>per host"]
    K --> L[Results]
Loading

JUNOS Upgrade Workflow

A firmware upgrade follows a four-step sequence designed to minimize risk. First, dry-run verifies connectivity, package availability, and checksum without making changes. Then upgrade copies and installs the package. version confirms the pending version matches expectations before scheduling the reboot. The reboot is scheduled separately so you can choose a maintenance window. If anything goes wrong, rollback reverts to the previous firmware at any point before reboot.

flowchart TD
    A["1. dry-run<br/>junos-ops upgrade -n"] --> B["2. upgrade<br/>junos-ops upgrade"]
    B --> C["3. version<br/>junos-ops version"]
    C --> D["4. reboot<br/>junos-ops reboot --at"]
    D -.->|"if problems"| E["rollback<br/>junos-ops rollback"]
Loading
1. Pre-flight check with dry-run
   junos-ops upgrade --dry-run hostname

2. Copy and install with upgrade
   junos-ops upgrade hostname

3. Verify version
   junos-ops version hostname

4. Schedule reboot
   junos-ops reboot --at 2506130500 hostname

Use rollback to revert to the previous version if problems occur.

Upgrade Internal Flow

The upgrade subcommand runs multiple safety checks before and during the update process. It first compares the running version against the target — skipping entirely if already up to date. If a different pending version exists, it rolls that back before proceeding. The copy phase frees disk space (storage cleanup + snapshot delete on EX/QFX), then transfers the package via safe_copy with checksum verification to detect corruption. Before installing, it clears any existing reboot schedule and saves the rescue config as a recovery baseline. Finally, sw.install() validates the package integrity on the device before applying it.

flowchart TD
    A[NETCONF connect] --> B{"Running version<br/>= target?"}
    B -->|yes| C([Skip — already up to date])
    B -->|no| D{"Pending version<br/>exists?"}
    D -->|no| E[copy]
    D -->|yes| F{Pending ≥ target?}
    F -->|yes, no --force| C
    F -->|no / --force| G[Rollback pending version]
    G --> E

    subgraph copy ["copy()"]
        E --> H[Storage cleanup]
        H --> I["Snapshot delete<br/>(EX/QFX only)"]
        I --> J["safe_copy via SCP<br/>+ checksum verification"]:::safe
    end

    J --> K[Clear reboot schedule]
    K --> L[Save rescue config]:::safe
    L --> M["sw.install()<br/>validate + checksum"]:::install
    M --> N([Done — reboot when ready])

    classDef safe fill:#d4edda,stroke:#28a745,color:#000
    classDef install fill:#cce5ff,stroke:#007bff,color:#000
Loading

Reboot Safety Flow

Before scheduling a reboot, reboot automatically checks whether the configuration was modified after the firmware install. If changes are detected, it re-saves the rescue config and re-installs with validation to ensure the new firmware is compatible with the current config.

flowchart TD
    A[NETCONF connect] --> B{"Existing reboot<br/>schedule?"}
    B -->|no| D
    B -->|yes| C{--force?}
    C -->|no| B2([Skip — keep existing schedule])
    C -->|yes| CL[Clear existing schedule] --> D

    D{"Pending version<br/>exists?"} -->|no| SCH
    D -->|yes| E[Get last commit time]
    E --> F[Get rescue config time]
    F --> G{"Config modified<br/>after install?"}
    G -->|no| SCH
    G -->|yes| H[Re-save rescue config]:::warned
    H --> I["Re-install firmware<br/>(validate + checksum)"]:::install
    I -->|success| SCH
    I -->|failure| ERR([Abort — do not reboot]):::errstyle

    SCH["Schedule reboot at<br/>--at YYMMDDHHMM"]:::safe

    classDef safe fill:#d4edda,stroke:#28a745,color:#000
    classDef install fill:#cce5ff,stroke:#007bff,color:#000
    classDef warned fill:#fff3cd,stroke:#ffc107,color:#000
    classDef errstyle fill:#f8d7da,stroke:#dc3545,color:#000
Loading

Config Push Workflow

The config subcommand uses a safe commit flow: commit confirmed (auto-rollback timer) → health checkcommit (permanent). If the health check fails, the final commit is withheld and JUNOS automatically rolls back when the timer expires.

See docs/config.md for full details including health check options (uptime, ping, CLI commands), commit confirmed flow, --no-confirm, --no-commit, and parallel execution.

1. Preview changes with dry-run
   junos-ops config -f commands.set --dry-run hostname

2. Apply changes
   junos-ops config -f commands.set hostname

3. Apply with NETCONF health check (no ping dependency)
   junos-ops config -f commands.set --health-check uptime hostname

Tag-based Host Filtering

Use --tags to target hosts by tags defined in config.ini. The flag combines two operators so you can build almost any selection:

  • AND within a group: comma-separated tags in one --tags value require the host to carry all of them (--tags tokyo,core → hosts tagged both tokyo AND core).
  • OR between groups: repeat --tags to add another group. A host matches if it satisfies any group (--tags main --tags backup → hosts tagged either main or backup).
  • AND with a hostname list: when explicit hostnames are also given, the tag filter and the hostname list intersect — only hostnames that also satisfy the tag filter are targeted.

In logical terms: (--tags group₁) OR (--tags group₂) OR … filters the host set, and [hostname …] further restricts that set to the listed names.

History: v0.16.3 and earlier treated --tags + hostnames as union. v0.16.4 switched to intersection so --tags reads as a safety filter ("narrow further by name"). v0.16.6 added repeatable --tags for OR between groups — previously the last --tags occurrence silently replaced earlier ones.

# All hosts tagged "tokyo"
junos-ops version --tags tokyo

# Hosts tagged both "tokyo" AND "core"
junos-ops version --tags tokyo,core

# Hosts tagged "main" OR "backup"
junos-ops check --remote --tags main --tags backup

# (tokyo AND core) OR access — any host that is a tokyo core router
# or carries the access tag.
junos-ops version --tags tokyo,core --tags access

# Among "backup"-tagged hosts, target only these two (tag filter +
# hostname list = intersection)
junos-ops copy --tags backup rt1.example.jp rt2.example.jp

Excluding hosts with --exclude-tags

--exclude-tags removes hosts from the selection using the same grammar as --tags. It runs after --tags and the hostname intersection, so it acts as a final "drop these" pass.

  • --exclude-tags a,b drops hosts tagged with both a and b (AND within a group).
  • Repeating the flag ORs groups, so --exclude-tags a --exclude-tags b drops hosts tagged with a or b.
  • --exclude-tags may be used on its own; without --tags or a hostname list it subtracts from the default "all sections" set.
  • If every candidate host is dropped, junos-ops exits with an error instead of silently no-op'ing.
# All hosts tagged "main" except SRX345 boxes
junos-ops check --remote --tags main --exclude-tags srx345

# Run against every host except the lab tier
junos-ops version --exclude-tags lab

# Drop SRX345 OR EOL hosts from the main set
junos-ops check --remote --tags main \
                --exclude-tags srx345 --exclude-tags eol

Examples

upgrade (package update)

% junos-ops upgrade rt1.example.jp
# rt1.example.jp
remote: jinstall-ppc-18.4R3-S10-signed.tgz is not found.
copy: system storage cleanup successful
rt1.example.jp: cleaning filesystem ...
rt1.example.jp: b'jinstall-ppc-18.4R3-S10-signed.tgz': 380102074 / 380102074 (100%)
rt1.example.jp: checksum check passed.
install: clear reboot schedule successful
install: rescue config save successful
rt1.example.jp: software validate package-result: 0

version (version check)

% junos-ops version rt1.example.jp
# rt1.example.jp
  - hostname: rt1
  - model: MX5-T
  - running version: 18.4R3-S7.2
  - planning version: 18.4R3-S10
    - running='18.4R3-S7.2' < planning='18.4R3-S10'
  - pending version: 18.4R3-S10
    - running='18.4R3-S7.2' < pending='18.4R3-S10' : Please plan to reboot.
  - reboot requested by exadmin at Sat Dec  4 05:00:00 2021

check (pre-flight verification)

Unified pre-flight checks across NETCONF reachability and firmware checksums. Exit code is non-zero if any check fails. Default (no flag) runs --connect only.

--local is inventory-mode and ignores hostnames — it iterates every <model>.file / <model>.hash pair in config.ini and verifies the files on the staging server. No NETCONF required:

% junos-ops check --local
lpath: /opt/firmware
model            file                                                        checksum
---------------  ----------------------------------------------------------  ----------
ex2300-24t       junos-arm-32-23.4R2-S7.4.tgz                                ok
ex3400-24t       junos-arm-32-23.4R2-S7.4.tgz                                ok(cached)
ex4300-32f       jinstall-ex-4300-21.4R3-S12.2-signed.tgz                    ok
mx5-t            jinstall-ppc-21.2R3-S8.5-signed.tgz                         missing

  mx5-t: - local package: /opt/firmware/jinstall-ppc-21.2R3-S8.5-signed.tgz is not found.

Use --model M to restrict the inventory to a single model. For multi-model selection, attach <model>.tags = ... to the relevant rows in config.ini and use --tags (and optionally --exclude-tags) — the same AND/OR grammar as host-side --tags applies, but selectors are matched against the model name and the model's <model>.tags set, not against host tags:

[DEFAULT]
ex2300-24t.file = junos-arm-32-23.4R2-S7.4.tgz
ex2300-24t.hash = ...
ex2300-24t.tags = main, edge       # optional model tags
mx240.file       = ...
mx240.tags       = backbone
# Single model — matches by name, no <model>.tags needed
% junos-ops check --local --tags ex2300-24t

# "main"-tagged models — uses <model>.tags
% junos-ops check --local --tags main

# Multi-selection: model name + tag groups OR together
% junos-ops check --local --tags srx345 --tags backbone

# Drop a model from the result
% junos-ops check --local --tags main --exclude-tags ex3400-24t

For --local-only runs, host-side --tags (the per-host tag selector) is not applied — --local is host-independent, so --tags reinterprets as the model selector described above. Mixing --local with --connect / --remote keeps the host selector active for the per-host part.

Note on --all (or --local --connect/--remote): a single --tags value filters both tables at once — the inventory table by <model>.tags (and model name) and the per-host table by host tags. The two tag namespaces are independent, so keep their names aligned (e.g. a main model tag and a main host tag) if you want --all --tags main to scope both consistently.

--connect / --remote (and --all) are per-host and use the specified hostnames (or every host in config.ini, optionally filtered by --tags). --remote doubles as a "did the SCP copy fully land" check between copy and install:

% junos-ops check --connect --remote rt1.example.jp rt2.example.jp
hostname         connect  remote      model     file
---------------  -------  ----------  --------  -----------------------------------
rt1.example.jp   ok       ok          MX5-T     jinstall-ppc-18.4R3-S10-signed.tgz
rt2.example.jp   ok       missing     MX5-T     jinstall-ppc-18.4R3-S10-signed.tgz

  rt2.example.jp: remote: - remote package: jinstall-ppc-18.4R3-S10-signed.tgz is not found.

When --connect / --remote need to resolve a model and it was not supplied via --model, they fall back to dev.facts["model"] from the live device, or to an optional model = MX5-T key added to the host section in config.ini.

--all runs both: the inventory table is printed first, then the per-host table.

rsi (parallel RSI/SCF collection)

% junos-ops rsi --workers 5 rt1.example.jp rt2.example.jp
# rt1.example.jp
  rt1.example.jp.SCF done
  rt1.example.jp.RSI done
# rt2.example.jp
  rt2.example.jp.SCF done
  rt2.example.jp.RSI done

The output directory comes from RSI_DIR in config.ini, but --rsi-dir DIR overrides it per run (default: current directory).

reboot (scheduled reboot)

% junos-ops reboot --at 2506130500 rt1.example.jp
# rt1.example.jp
	Shutdown at Fri Jun 13 05:00:00 2025. [pid 97978]

snapshot (sync the alternate boot media)

% junos-ops snapshot rt1.example.jp
# rt1.example.jp
	snapshot: 'request system snapshot' completed

Runs request system snapshot to copy the running system (root + config) onto the alternate (backup) boot media.

A JUNOS upgrade only rewrites the currently-running media; the alternate is not refreshed automatically and drifts over time ("fossil alternate"). If the primary later fails to boot, the device falls back to that stale alternate and may come up non-routable. Taking a snapshot — before a risky change, or periodically — keeps the alternate current so the fallback is safe.

  • MX is the primary target (both fixed-config MX5/MX80 dual-eUSB and RE/disk-based MX240/480), where this problem actually bites.
  • EX/QFX (SWITCH) are supported, but EX2300/EX3400 are chronically tight on free disk and the recovery snapshot may not fit; an out-of-space failure is reported as a clean, non-fatal skip rather than an error.
  • Branch SRX (SRX300/320/340/345/380) are supported but low priority — they keep the alternate slice in sync during normal upgrades, so an explicit snapshot is usually unnecessary (uses request system snapshot slice alternate).
  • Other platforms (vmhost MX such as MX204/MX10003, mid/high-end SRX, HA clusters, vMX/vSRX, Junos Evolved, unknown) are skipped (non-fatal) — no command is issued on hardware whose snapshot behaviour is not verified.

Safety guard: snapshot refuses to run on a device that appears to be running on its alternate media (which would clone a possibly-stale system onto the primary). Override with --force only when you are sure the running image is the one you want to propagate. The on-device detection of the alternate-media state is best-effort and may report "inconclusive" (the snapshot then proceeds with a warning).

config (push set command file)

Push a set-format command file to multiple devices. Uses a safe commit flow: commit check, commit confirmed, then confirm.

% cat add-user.set
set system login user viewer class read-only
set system login user viewer authentication ssh-ed25519 "ssh-ed25519 AAAA..."

% junos-ops config -f add-user.set --dry-run rt1.example.jp rt2.example.jp
# rt1.example.jp
[edit system login]
+    user viewer {
+        class read-only;
+        authentication {
+            ssh-ed25519 "ssh-ed25519 AAAA...";
+        }
+    }
	dry-run: rollback (no commit)
# rt2.example.jp
	...

% junos-ops config -f add-user.set rt1.example.jp rt2.example.jp
# rt1.example.jp
	...
	commit check passed
	commit confirmed 1 applied
	health check: uptime (NETCONF RPC)
	health check passed (uptime: 2026-04-16 13:20:31 JST)
	commit confirmed, changes are now permanent
# rt2.example.jp
	...

Use --confirm N to change the commit confirmed timeout (default: 1 minute). Use --no-health-check to skip the post-commit health check.

Set files can include # comments and blank lines — they are automatically stripped before sending to the device.

Jinja2 Templates

Use .j2 files to generate per-host configurations from a single template. Variables come from var_* keys in config.ini and device facts.

junos-ops config -f ntp.set.j2 --dry-run rt1.example.jp sw1.example.jp
junos-ops config -f ntp.set.j2 rt1.example.jp sw1.example.jp

See docs/template.md for details, usage patterns (conditionals, loops), and config.ini examples.

show (run CLI command)

Run an arbitrary CLI command across multiple devices in parallel. Use --retry N to automatically retry on RpcTimeoutError (useful for large-scale parallel execution).

% junos-ops show "show bgp summary" --config accounts.ini gw1.example.jp gw2.example.jp
# gw1.example.jp
Groups: 4 Peers: 6 Down peers: 0
...
# gw2.example.jp
Groups: 3 Peers: 4 Down peers: 0
...

Use -f to run multiple commands from a file within a single NETCONF session per device:

% cat commands.txt
# security policy check
show security policies hit-count
show security flow session summary

% junos-ops show -f commands.txt --config accounts.ini fw1.example.jp
# fw1.example.jp
## show security policies hit-count
...

## show security flow session summary
...

Use --retry N to retry commands that fail with RpcTimeoutError (with incremental backoff: 5s, 10s, 15s, ...):

% junos-ops show "show system alarms" --retry 2 --workers 10 --config accounts.ini

Structured output (--format)

Use -F / --format {text,json,xml} to change the output format. text is the default (legacy behaviour); json is handy for programmatic consumers and AI assistants; xml returns the pretty-printed RPC reply.

% junos-ops show "show interfaces terse" --format json gw1.example.jp
# gw1.example.jp
## show interfaces terse
{
  "interface-information": {
    "physical-interface": [ ... ]
  }
}

Caveat — pipe stages are ignored by the device regardless of --format. dev.cli() sends the command over NETCONF RPC, and the device does not process pipe stages for that path — modifiers like | match, | last, and | count are silently ignored no matter which format is requested, text included (see Juniper/junos-mcp-server#4 / #12, whose own NETCONF trace shows a format="text" request still losing its pipe stage). The one exception is | display xml rpc, which the device honours specially to return the equivalent RPC method name instead of normal output. If you need to filter output, pipe it through shell tools client-side (e.g. grep) or call the equivalent RPC directly. For json, note that -F json alone still wraps output in the # hostname / ## command headers shown above — combine it with the top-level --json flag (junos-ops --json show ... -F json host) to get clean, header-free JSON on stdout before piping to jq.

No subcommand (show device facts)

% junos-ops gw1.example.jp
# gw1.example.jp
{'2RE': True,
 'hostname': 'gw1',
 'model': 'MX240',
 'version': '18.4R3-S7.2',
 ...}

Upgrading low-flash devices (--unlink)

On low-flash devices like EX2300/EX3400 (/dev/gpt/junos = 1.3GB), major upgrades such as 22.4 → 23.4 may fail at the validation step with ERROR: insufficient space. This is because PyEZ's SW.install() does not expose the unlink option of request system software add — its default path never asks pkgadd to unlink the source tgz, so the original package occupies space throughout extraction.

Pass --unlink to invoke request system software add <package> unlink directly via the CLI, so pkgadd unlinks the tgz during installation and frees space as it extracts.

# For low-flash devices
junos-ops upgrade --unlink ex3400-host.example.jp

# Same flag is available on the install-only subcommand
junos-ops install --unlink ex3400-host.example.jp

Supported Models

Any Juniper model can be supported by defining the model name and package file in the configuration file. Models included in the example configuration:

Series Example Models
EX EX2300-24T, EX3400-24T, EX4300-32F
MX MX5-T, MX240
QFX QFX5110-48S-4C
SRX SRX300, SRX345, SRX1500, SRX4600

License

Apache License 2.0

Copyright 2022-2025 AIKAWA Shigechika

About

Python CLI to automate Juniper/JUNOS operations over NETCONF: model-aware upgrade, rollback, reboot, config push, and RSI/SCF collection

Topics

Resources

License

Stars

3 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages