net-connector 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +82 -0
- data/LICENSE +21 -0
- data/README.md +277 -0
- data/docs/RELEASING.md +81 -0
- data/docs/VERIFICATION.md +73 -0
- data/docs/architecture.md +268 -0
- data/exe/net-connector-backup +6 -0
- data/lib/net/connector/device/base.rb +368 -0
- data/lib/net/connector/device/interface_description.rb +41 -0
- data/lib/net/connector/device/interface_name.rb +56 -0
- data/lib/net/connector/device/profile.rb +446 -0
- data/lib/net/connector/device/running_config/strategy.rb +29 -0
- data/lib/net/connector/device/running_config.rb +69 -0
- data/lib/net/connector/engine/authentication.rb +77 -0
- data/lib/net/connector/engine/base.rb +4 -0
- data/lib/net/connector/engine/command.rb +101 -0
- data/lib/net/connector/engine/configuration.rb +131 -0
- data/lib/net/connector/engine/core.rb +6 -0
- data/lib/net/connector/engine/dialogue.rb +223 -0
- data/lib/net/connector/engine/errors.rb +183 -0
- data/lib/net/connector/engine/execution.rb +64 -0
- data/lib/net/connector/engine/log.rb +227 -0
- data/lib/net/connector/engine/log_messages.rb +47 -0
- data/lib/net/connector/engine/profile.rb +4 -0
- data/lib/net/connector/engine/recovery.rb +57 -0
- data/lib/net/connector/engine/result.rb +67 -0
- data/lib/net/connector/engine/session.rb +333 -0
- data/lib/net/connector/engine/terminal_renderer.rb +158 -0
- data/lib/net/connector/engine/transport.rb +184 -0
- data/lib/net/connector/engine.rb +4 -0
- data/lib/net/connector/netdisco/batch.rb +71 -0
- data/lib/net/connector/netdisco/cli.rb +142 -0
- data/lib/net/connector/netdisco/client.rb +148 -0
- data/lib/net/connector/netdisco/config_file.rb +87 -0
- data/lib/net/connector/netdisco/device.rb +75 -0
- data/lib/net/connector/netdisco/fleet.rb +258 -0
- data/lib/net/connector/netdisco/planner.rb +77 -0
- data/lib/net/connector/netdisco/result_store.rb +43 -0
- data/lib/net/connector/netdisco/rules.rb +131 -0
- data/lib/net/connector/netdisco/settings.rb +183 -0
- data/lib/net/connector/netdisco/worker.rb +75 -0
- data/lib/net/connector/netdisco.rb +14 -0
- data/lib/net/connector/operations/local_backup.rb +49 -0
- data/lib/net/connector/operations/parse_output.rb +60 -0
- data/lib/net/connector/operations/private_file.rb +25 -0
- data/lib/net/connector/operations/running_config/cisco.rb +14 -0
- data/lib/net/connector/operations/running_config/hillstone.rb +12 -0
- data/lib/net/connector/operations/running_config/palo_alto.rb +12 -0
- data/lib/net/connector/operations/running_config/strategy.rb +3 -0
- data/lib/net/connector/operations/running_config.rb +16 -0
- data/lib/net/connector/operations/saved_config.rb +72 -0
- data/lib/net/connector/operations/tftp/cisco_ios.rb +11 -0
- data/lib/net/connector/operations/tftp/cisco_nxos.rb +11 -0
- data/lib/net/connector/operations/tftp/h3c.rb +11 -0
- data/lib/net/connector/operations/tftp/hillstone.rb +11 -0
- data/lib/net/connector/operations/tftp/huawei.rb +11 -0
- data/lib/net/connector/operations/tftp/palo_alto.rb +11 -0
- data/lib/net/connector/operations/tftp/radware.rb +11 -0
- data/lib/net/connector/operations/tftp/strategy.rb +77 -0
- data/lib/net/connector/operations/tftp_backup.rb +120 -0
- data/lib/net/connector/operations/topology/cisco.rb +11 -0
- data/lib/net/connector/operations/topology/h3c.rb +11 -0
- data/lib/net/connector/operations/topology/hillstone.rb +11 -0
- data/lib/net/connector/operations/topology/palo_alto.rb +11 -0
- data/lib/net/connector/operations/topology/radware.rb +11 -0
- data/lib/net/connector/operations/topology/strategy.rb +53 -0
- data/lib/net/connector/operations/topology.rb +192 -0
- data/lib/net/connector/operations.rb +20 -0
- data/lib/net/connector/templates/cisco_cdp_neighbors_detail.textfsm +7 -0
- data/lib/net/connector/templates/cisco_ios_running_config_interfaces.textfsm +12 -0
- data/lib/net/connector/templates/cisco_ios_show_ip_interface_brief.textfsm +7 -0
- data/lib/net/connector/templates/h3c_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/h3c_lldp_local_first.textfsm +8 -0
- data/lib/net/connector/templates/h3c_lldp_name_first.textfsm +8 -0
- data/lib/net/connector/templates/hillstone_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/hillstone_lldp_neighbors.textfsm +7 -0
- data/lib/net/connector/templates/index +8 -0
- data/lib/net/connector/templates/palo_alto_interface_descriptions.textfsm +6 -0
- data/lib/net/connector/templates/palo_alto_lldp_neighbors.textfsm +11 -0
- data/lib/net/connector/templates/radware_port_names.textfsm +13 -0
- data/lib/net/connector/vendor/cisco_ios/running_config.rb +19 -0
- data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +32 -0
- data/lib/net/connector/vendor/cisco_ios/topology.rb +33 -0
- data/lib/net/connector/vendor/cisco_ios.rb +48 -0
- data/lib/net/connector/vendor/cisco_nxos/running_config.rb +22 -0
- data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/cisco_nxos.rb +55 -0
- data/lib/net/connector/vendor/h3c/tftp_backup.rb +43 -0
- data/lib/net/connector/vendor/h3c/topology.rb +56 -0
- data/lib/net/connector/vendor/h3c.rb +77 -0
- data/lib/net/connector/vendor/h3c_wireless.rb +14 -0
- data/lib/net/connector/vendor/hillstone/running_config.rb +13 -0
- data/lib/net/connector/vendor/hillstone/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/hillstone/topology.rb +48 -0
- data/lib/net/connector/vendor/hillstone.rb +43 -0
- data/lib/net/connector/vendor/huawei/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/huawei.rb +64 -0
- data/lib/net/connector/vendor/palo_alto/running_config.rb +70 -0
- data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +36 -0
- data/lib/net/connector/vendor/palo_alto/topology.rb +63 -0
- data/lib/net/connector/vendor/palo_alto.rb +43 -0
- data/lib/net/connector/vendor/radware/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/radware/topology.rb +14 -0
- data/lib/net/connector/vendor/radware.rb +62 -0
- data/lib/net/connector/version.rb +7 -0
- data/lib/net/connector.rb +48 -0
- data/lib/net-connector.rb +3 -0
- metadata +517 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a0a3f3d7d955f359e45f5ce37c02b2043f809e2a55a2017fb159b24252e3ca02
|
|
4
|
+
data.tar.gz: b8b0bdea1f9ba9129cc2846cbed0beea71086133ab220be758da7797863a6b8d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 317766ae3e494407065da741a6006a32e9236769fdf377986d72d1ea554ab97d022a63b53e946c7be4f04f582f1f63f5241854a712e70da34833c21513981471
|
|
7
|
+
data.tar.gz: ec899fbc55fab9195431769f464b5f45b2ad9fa78d14f8e98fcb2e2fe1a46fb4d85a700ab80c4babaad32ec82ce795c53844f79414de669d076890bf81417275
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.4.0 - 2026-09-26
|
|
6
|
+
|
|
7
|
+
- Redact dynamic login challenge failures before logging or capturing their underlying exceptions, including partial credentials and backtraces.
|
|
8
|
+
- Keep PAN-OS LLDP discovery usable when some local interfaces have no neighbors, while rejecting incomplete nonempty records.
|
|
9
|
+
- Stop remaining batch workers after an interruption, close incomplete command sessions, and use unique directories for concurrent example backups.
|
|
10
|
+
- Detect additional network device credential syntax before publishing, including privileged IOS usernames and hashed local passwords.
|
|
11
|
+
- Add shared local/CI checks on Ruby 3.2–4.0 for Linux and macOS, pinned workflow tooling, isolated gem installation, and a real local PTY smoke test.
|
|
12
|
+
- Require `expect-pty` 0.3.1 or later in the 0.3 series, declare directly used standard-library gems, and keep development dependencies compatible with Ruby 3.2.
|
|
13
|
+
- Add artifact-preserving local and manual CI release scripts with version, changelog, Git, metadata, file-byte and remote-checksum verification.
|
|
14
|
+
- Scan source, available Git history and the built gem for credentials and private network addresses; redact reports, replace example addresses and credential placeholders, and ignore local configurations, backups, logs and credentials.
|
|
15
|
+
- Make running configuration a device capability under `device/`; move the facade and profile there, and bind one collection strategy per call while preserving inherited method hooks and configuration bytes.
|
|
16
|
+
- Co-locate vendor collection, TFTP and topology rules under `vendor/<name>/`; retain old require paths and constants as forwarding aliases, and load vendors and TextFSM only when needed.
|
|
17
|
+
- Share interface name matching, description formatting and interface-view command construction. Description plans now abbreviate known neighbor ports by default while preserving case; `abbreviate: false` retains previous formatting and `lowercase: true` is opt-in. Raw evidence, local command names, confirmation and readback remain unchanged.
|
|
18
|
+
- Bind collection to the complete known session prompt so configuration descriptions cannot terminate a response early; incomplete responses preserve existing backups.
|
|
19
|
+
- Require explicit TFTP completion messages, excluding command/reply/prompt echoes and retaining failure evidence across terminal edits.
|
|
20
|
+
- Keep temporary secrets through command preparation, postprocessing, callbacks and exception normalization; redact secrets containing `[REDACTED]` in direct and streaming output.
|
|
21
|
+
- Hold one session operation lease across topology revalidation, changes and readback while rejecting nested callbacks and concurrent callers.
|
|
22
|
+
- Use normalized management IPs for local backup filenames, preserving rename baselines and unique legacy-file compatibility without deleting old files.
|
|
23
|
+
|
|
24
|
+
- Reject zero-width prompt matches so an old prompt cannot complete a later command, and map declared terminal width/height to PTY rows/columns correctly.
|
|
25
|
+
- Reject prompt-only and command-echo-only configuration responses while preserving completed steps and existing backup files.
|
|
26
|
+
- Reject unknown H3C/Hillstone neighbor rows instead of treating partial output as an empty or complete table; ignore H3C discovery command echoes in TextFSM parsing.
|
|
27
|
+
- Recheck complete neighbor identity, including chassis ID, before applying description plans and reject truncated PAN-OS multiline comment evidence.
|
|
28
|
+
- Recognize colorized command and TFTP failures while retaining failures overwritten by terminal controls.
|
|
29
|
+
- Share safe TFTP filename generation and length limits between direct operations and inventory batches, including scoped IPv6 addresses.
|
|
30
|
+
- Close log files when initialization fails and clear owned log state after close failures.
|
|
31
|
+
- Simplify the private Profile block guard to `check_block!` and document Expect semantics and resource ownership.
|
|
32
|
+
|
|
33
|
+
- Bind vendor TFTP and topology strategies through the existing Profile DSL, and expose read-only `supports?` capability queries.
|
|
34
|
+
- Keep configuration collection in one locked execution path, including PAN-OS step selection; reject missing, blank, or invalid cleaned configuration as incomplete.
|
|
35
|
+
- Move vendor-specific topology commands, parsing evidence, interface spelling, and commit rules into strategies while retaining plan revalidation and readback.
|
|
36
|
+
|
|
37
|
+
- Redact configured credentials across log chunks and terminal rendering, reject raw application loggers consistently, and release stale session state before reconnecting.
|
|
38
|
+
- Commit PAN-OS interface descriptions before leaving configuration mode and parse NX-OS indented descriptions correctly.
|
|
39
|
+
- Keep TFTP preview filenames consistent with execution and avoid treating diagnostic words inside filenames as transfer failures.
|
|
40
|
+
- Suppress sensitive underlying Netdisco exceptions and preserve empty or invalid-inventory outcomes in backup examples.
|
|
41
|
+
|
|
42
|
+
- Reject inconsistent inventory plans before device I/O, preserve immutable device snapshots, and require a backup artifact before reporting success.
|
|
43
|
+
- Mark empty backup batches as `no_devices` and reject `--host` addresses absent from the inventory.
|
|
44
|
+
- Reuse the private atomic file writer for batch JSON reports.
|
|
45
|
+
- Use Active Support tagged logging for session events, with debug device output in the same log file.
|
|
46
|
+
- Separate vendor CLI profiles from configuration collection, local backup, and TFTP export operation objects.
|
|
47
|
+
- Move each vendor's TFTP command, prompt, source-file, and completion rules into a dedicated transfer strategy.
|
|
48
|
+
- Add a backup CLI with safe non-secret YAML settings, effective-config display, inventory preview, targeted runs, and TFTP batch execution.
|
|
49
|
+
- Export an existing local device configuration to stdout or a private file without reconnecting to Netdisco or the device.
|
|
50
|
+
- Compare local configuration backups by SHA-256, preserve unchanged files, and expose created/changed/unchanged states.
|
|
51
|
+
- Add per-device start and result callbacks plus change-only notification callbacks, with isolated callback failures and task timing.
|
|
52
|
+
- Share batch worker and device lifecycle handling between local and TFTP backups.
|
|
53
|
+
- Use `vrf:` for NX-OS and Hillstone device exports, and per-vendor `vrfs:` for fleet exports.
|
|
54
|
+
- Move Netdisco batch planning and worker dispatch out of the examples; keep device failures independent.
|
|
55
|
+
- Add batch execution summaries with private JSON reports by default and an injected database repository option.
|
|
56
|
+
- Add Hillstone StoneOS running-configuration collection and native startup-configuration TFTP export.
|
|
57
|
+
- Complete Radware Alteon TFTP prompts for `.tgz` filename, private-key choice, and `mansync`.
|
|
58
|
+
- Distinguish explicit device-side TFTP failures from transfers without a success confirmation.
|
|
59
|
+
- Add configurable session log levels, readable login and command events, full debug device output, and TFTP outcome events.
|
|
60
|
+
- Render session events as human-readable Chinese actions with local timestamps, while retaining full device output at debug level.
|
|
61
|
+
- Add concise per-device TFTP results to the end of each session log.
|
|
62
|
+
- Match the observed PAN-OS TFTP export command order and require a positive `Sent ... bytes` completion line.
|
|
63
|
+
- Allow full-inventory TFTP batches with 50 workers while preserving per-device outcomes and PAN-OS filename-collision protection.
|
|
64
|
+
- Discover H3C startup paths from each device, recognize completed TFTP progress, and support host-specific Netdisco connector overrides.
|
|
65
|
+
- Add a session-log review for full TFTP batches without rewriting original outcomes.
|
|
66
|
+
|
|
67
|
+
## 0.3.0
|
|
68
|
+
|
|
69
|
+
- Add device-initiated native TFTP backup with vendor-specific commands and transfer checks.
|
|
70
|
+
- Save Netdisco backups as sanitized `<device name>-<IP>.txt` files for easier lookup.
|
|
71
|
+
- Render terminal carriage returns in H3C and Huawei configuration backups.
|
|
72
|
+
|
|
73
|
+
## 0.2.0
|
|
74
|
+
|
|
75
|
+
- Move shared connector implementation into `engine/` and remove the obsolete top-level core files.
|
|
76
|
+
- Add a standalone Netdisco client with validated inventory pagination and legacy query support.
|
|
77
|
+
- Map discovered devices to connector profiles with configurable selection and mapping rules.
|
|
78
|
+
- Run bounded concurrent backups with environment-backed credentials, paths, and per-device outcomes.
|
|
79
|
+
|
|
80
|
+
## 0.1.0
|
|
81
|
+
|
|
82
|
+
- Initial standalone connector with SSH and Telnet sessions, script execution, configuration collection, logging, and seven vendor profiles.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 net-connector contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# net-connector
|
|
2
|
+
|
|
3
|
+
`net-connector` runs network device CLI sessions over SSH or Telnet. It collects running configuration, writes private backup files, executes command scripts, answers device prompts, records redacted session logs, and returns structured errors and partial results.
|
|
4
|
+
|
|
5
|
+
The code is organized by responsibility: `lib/net/connector/engine/` owns sessions, transport, scripts, results, and logging; `device/` owns the device facade, profile, running configuration and interface text helpers; `vendor/<name>.rb` assembles that vendor's rules from `vendor/<name>/running_config.rb`, `tftp_backup.rb` and `topology.rb` where needed; `operations/` owns shared backup, parsing and topology workflows; `netdisco/` owns inventory and batch orchestration. Identical vendor rules are shared. See [docs/architecture.md](docs/architecture.md) for the model. Load the public API with `require "net/connector"` or the Netdisco integration with `require "net/connector/netdisco"`. The public API loads vendor rules and parsing on demand; `require "net/connector/engine/core"` loads only the session execution layer.
|
|
6
|
+
|
|
7
|
+
Ruby 3.2+ and a POSIX system are required. SSH uses the local OpenSSH client. Telnet requires the local `telnet` program and must be selected explicitly. The gem depends on [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.3.x (at least 0.3.1) and [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.x.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
gem install net-connector
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "net/connector"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Supported devices
|
|
20
|
+
|
|
21
|
+
Use `device.supports?(:tftp_backup)` or another capability to check whether a
|
|
22
|
+
connector implements an operation without contacting the device. The complete
|
|
23
|
+
[capability matrix and extension example](docs/architecture.md#vendor-capabilities)
|
|
24
|
+
cover collection, saving, TFTP, neighbor discovery, and description changes.
|
|
25
|
+
This check does not test device permissions or firmware behavior.
|
|
26
|
+
|
|
27
|
+
| Vendor key | Device family | Running configuration | Save configuration |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `:h3c` | H3C Comware | `dis cur` | `save force` |
|
|
30
|
+
| `:h3c_wireless` | H3C wireless controller, Comware CLI | `dis cur` | `save force` |
|
|
31
|
+
| `:cisco_ios` | Cisco IOS / IOS XE | `show running-config` | `copy running-config startup-config` |
|
|
32
|
+
| `:cisco_nxos` | Cisco NX-OS | `show running-config` | `copy run start` |
|
|
33
|
+
| `:radware` | Radware Alteon CLI | `/cfg/dump` | `/cfg/save` |
|
|
34
|
+
| `:palo_alto` | Palo Alto PAN-OS CLI | set format candidate export | unsupported |
|
|
35
|
+
| `:huawei` | Huawei CLI | `dis cur` | `save force` |
|
|
36
|
+
| `:hillstone` | Hillstone StoneOS | `show configuration running` | `save all` |
|
|
37
|
+
|
|
38
|
+
Aliases `:cisco_n9k` and `:paloalto` are accepted. Vendor classes live directly under `Net::Connector`, such as `Net::Connector::H3cWireless::Connector`.
|
|
39
|
+
|
|
40
|
+
## Login and backup
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
Net::Connector.open(:cisco_ios,
|
|
44
|
+
host: "192.0.2.10", username: "admin", password: ENV.fetch("DEVICE_PASSWORD"),
|
|
45
|
+
known_hosts: "/etc/net-connector/known_hosts", host_key_policy: :strict,
|
|
46
|
+
log_file: "/var/log/net-connector/router.log") do |device|
|
|
47
|
+
backup = device.backup(path: "/var/backups/router-running.cfg")
|
|
48
|
+
puts "#{backup.bytes} bytes, SHA-256 #{backup.sha256}"
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`open` closes the session even when the block fails. `backup` collects first, then atomically replaces the requested file with mode `0600`. Collection failure leaves an existing file unchanged. It returns `Backup(path:, bytes:, sha256:, collected_at:)`. The caller must create the destination directory and protect backups because running configurations can contain device secrets.
|
|
53
|
+
|
|
54
|
+
## Native TFTP backup
|
|
55
|
+
|
|
56
|
+
`tftp_backup` asks the device to send its native configuration directly to a TFTP server. Each vendor supplies its own command and prompt handling. For example, Huawei and H3C use a device file as the source:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
Net::Connector.open(:huawei, host: "192.0.2.20", username: ENV.fetch("DEVICE_USERNAME"),
|
|
60
|
+
password: ENV.fetch("DEVICE_PASSWORD")) do |device|
|
|
61
|
+
transfer = device.tftp_backup(host: "192.0.2.30", source_file: "flash:/startup.cfg")
|
|
62
|
+
puts "Uploaded #{transfer.path} to #{transfer.server}"
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This emits `tftp 192.0.2.30 put flash:/startup.cfg`; pass `path: "site/switch.cfg"` to choose a different remote filename. H3C discovers its saved startup file with `display startup` unless `source_file:` is given. Huawei requires `source_file:` because its saved file location varies by model. Cisco IOS exports `running-config` using its interactive `copy running-config tftp:` flow. Cisco Nexus 9000 exports `running-config` with `vrf management` by default; pass `vrf: "other-vrf"` to override it. Hillstone exports the saved startup configuration with `export configuration startup to tftp server <server> <filename>`; its separate running-configuration query uses `show configuration running`. Radware Alteon uses `/cfg/ptcfg <server> -tftp`, produces a `.tgz` file, declines private-key export, and answers `mansync` for the internal-index prompt. Palo Alto exports `running-config.xml` and requires its fixed remote filename. All other vendors default the remote filename to `<management IP>.cfg`. The method returns `TftpBackup(server:, path:, completed_at:)` only when the device output confirms a transfer. Explicit device-side transfer failures use `:transfer_failed`; a missing success confirmation uses `:transfer_unconfirmed`. It does not read back the file from the TFTP server. TFTP carries configuration data without encryption; use it only on an appropriate management network.
|
|
67
|
+
|
|
68
|
+
The runnable [examples/tftp_backup.rb](examples/tftp_backup.rb) reads `DEVICE_VENDOR`, `DEVICE_HOST`, `DEVICE_USERNAME`, `DEVICE_PASSWORD`, and `TFTP_HOST` from the environment. Set `TFTP_SOURCE_FILE` for H3C or Huawei, `TFTP_PATH` for a remote filename, or `TFTP_VRF` to override the NX-OS VRF or Hillstone vrouter.
|
|
69
|
+
|
|
70
|
+
For in-memory collection, call `device.running_config`. It returns a `Result`; `result.value!` yields the cleaned configuration or raises its typed error.
|
|
71
|
+
|
|
72
|
+
## TextFSM parsing
|
|
73
|
+
|
|
74
|
+
`parse_command` executes one CLI command, then selects a TextFSM template by vendor and command. The bundled index covers Cisco IOS `show ip interface brief` and the CDP/LLDP commands used by topology discovery:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
Net::Connector.open(:cisco_ios, host: "192.0.2.10", username: "admin",
|
|
78
|
+
password: ENV.fetch("DEVICE_PASSWORD")) do |device|
|
|
79
|
+
interfaces = device.parse_command("show ip interface brief")
|
|
80
|
+
puts interfaces.first.fetch("INTERFACE")
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`parse_config` collects the cleaned running configuration and requires an explicit template. The bundled Cisco IOS template extracts interface names and descriptions; it is not a complete configuration model:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
interfaces = device.parse_config(template: "cisco_ios_running_config_interfaces.textfsm")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Both methods return an Array of Hash records using the template's field names. A valid template with no matching records returns `[]`. A missing or invalid template raises `Net::Connector::ParsingError`; a failed device command retains its original connector error. To use other vendors or commands, provide `template: "/path/to/template.textfsm"`, or `template_dir: "/path/to/templates"` with a TextFSM `index` containing `Template, Vendor, Command` columns. Each parse creates a fresh parser, so concurrent device tasks do not share parsing state. Existing local backups can be parsed without connecting to a device:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
saved = Net::Connector::Operations::SavedConfig.new(directory: "/var/backups")
|
|
94
|
+
rows = saved.parse(host: "192.0.2.10", template: "/path/to/template.textfsm")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Neighbors and interface descriptions
|
|
98
|
+
|
|
99
|
+
`neighbors` queries CDP on Cisco IOS/NX-OS and LLDP on H3C, H3C wireless, Hillstone, and PAN-OS. It returns records with `local_interface`, `neighbor_name`, `neighbor_interface`, `chassis_id`, and `protocol`. H3C LLDP list output is selected by its column header, covering releases that put the system name first or last. Unknown output raises `ParsingError`; it is not treated as an empty neighbor list. `interface_descriptions` reads the current running configuration, including Alteon port names in a Radware configuration dump.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Net::Connector.open(:h3c, host: "192.0.2.10", username: ENV.fetch("DEVICE_USERNAME"),
|
|
103
|
+
password: ENV.fetch("DEVICE_PASSWORD")) do |device|
|
|
104
|
+
plan = device.plan_interface_descriptions
|
|
105
|
+
plan.changes.each { |change| puts "#{change.interface}: #{change.old_description.inspect} -> #{change.new_description.inspect}" }
|
|
106
|
+
puts plan.commands.join("\n")
|
|
107
|
+
# Review the exact commands and obtain operator approval before applying.
|
|
108
|
+
result = device.apply_interface_descriptions(plan, confirmed: true)
|
|
109
|
+
result.value!
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The default proposal is `To <neighbor name> <abbreviated neighbor port>`. Abbreviation is enabled and preserves case: `ethernet1/1` becomes `eth1/1`, `Ethernet1/1` becomes `Eth1/1`, and `GigabitEthernet1/0/1` becomes `Gi1/0/1`. Unknown interface forms remain unchanged. Raw neighbors and plan evidence retain their original values; local command interface names are not abbreviated. Use `plan_interface_descriptions(abbreviate: false)` for the previous default, or `lowercase: true` to lowercase the remote port. Pass a block to control the complete description using the original neighbor. The pure common formatter is also available as `Net::Connector::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)`. Planning rejects ambiguous neighbors, missing peer identities, unsafe text, and unrecognized output. Applying requires `confirmed: true` and reads neighbors and old descriptions again; changed evidence raises `:stale_plan`. The plan includes the vendor's save command; PAN-OS uses `commit`. After the script, the operation reads the configuration back and reports `:description_unconfirmed` if the new text is absent. A failed script returns a `Result` with completed steps and its error. Radware Alteon can advertise LLDP on documented versions, but its documented CLI does not provide neighbor discovery; `neighbors` therefore raises `:neighbor_discovery_unsupported`. Its port names remain readable through `interface_descriptions`. Validate commands and output against the specific firmware before using an approved plan on a live device.
|
|
114
|
+
|
|
115
|
+
PAN-OS collection checks for pending candidate changes before and after export, and rejects XML or non-set output. This avoids reporting an ambiguous candidate configuration as a running configuration backup. Device-specific CLI and firmware differences still need validation against the target device.
|
|
116
|
+
|
|
117
|
+
## Scripts and automatic interaction
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
script = Net::Connector::Script.parse(<<~CLI, name: "change-123")
|
|
121
|
+
# Comments on their own line are ignored.
|
|
122
|
+
configure terminal
|
|
123
|
+
interface GigabitEthernet1/0/1
|
|
124
|
+
description uplink
|
|
125
|
+
end
|
|
126
|
+
CLI
|
|
127
|
+
|
|
128
|
+
result = device.execute_script(script) do |step|
|
|
129
|
+
puts "#{step.command.text}: #{step.duration.round(2)}s"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if result.failure?
|
|
133
|
+
warn "#{result.error.code} at #{result.error.phase}"
|
|
134
|
+
warn "#{result.steps.size} commands completed before failure"
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`execute` sends one command; `execute_script` accepts a `Script` or an array of commands. `Script.load(path)` reads a file. Scripts are validated before any device I/O. A result retains completed steps when a later command fails. The library does not replay commands after failure. `save_config` explicitly sends the vendor's save command when supported; script execution does not save automatically.
|
|
139
|
+
|
|
140
|
+
Vendor profiles cover pager prompts and common confirmation prompts. For a command-specific dialogue, pass `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]` to `execute`. Sensitive commands and responses pause session logging and are redacted in errors. Do not place secrets in ordinary command text unless `sensitive: true` is set.
|
|
141
|
+
|
|
142
|
+
## Connection settings
|
|
143
|
+
|
|
144
|
+
`Configuration` accepts `protocol: :ssh` (default) or `:telnet`, `port`, `login_timeout`, `command_timeout`, `write_timeout`, `max_output_bytes`, `log_file`, `logger`, `log_format: :text` or `:raw`, `log_level: :info` (default), and `known_hosts`. Text logs use `ActiveSupport::Logger` and `TaggedLogging`: each line has a local timestamp, severity, device tag, and readable Chinese action. At `:info`, one `log_file` contains connection, login, command, and TFTP outcomes. At `:debug`, that same file also contains sanitized login and device output plus command timing. No separate transcript file is created. `:warn` and `:error` retain only events at those levels or higher. The TFTP example defaults to `:debug`; set `NET_CONNECTOR_LOG_LEVEL` to change it. `log_format: :raw` writes only device bytes to `log_file` without event metadata. Configured credentials and sensitive command interactions are excluded from device output. Inject a Rails logger with `logger: Rails.logger` to send tagged events to the host application; the connector does not close or change its level. Host key policy defaults to `:strict`; `:accept_new` allows first-contact keys, while `:replace` requires an explicit known-hosts file. `telnet_fallback` and `legacy_ssh` are disabled by default and only apply to known connection failures. Commands are passed as argv, without a shell.
|
|
145
|
+
|
|
146
|
+
Telnet sends credentials without SSH encryption. Enable it only on a trusted management network. The gem does not check device authorization or review change plans; callers must enforce their own operational approval flow.
|
|
147
|
+
|
|
148
|
+
## Netdisco inventory and batch backup
|
|
149
|
+
|
|
150
|
+
`Net::Connector::Netdisco` reads the full Netdisco device inventory, maps supported rows to connector instances, then runs backups with a bounded number of worker threads. Netdisco supplies only inventory fields; device login credentials come from your environment or a resolver you provide. Inventory is fetched and validated before any device session starts. Unsupported, filtered, duplicate, missing-credential, failed, and saved-with-close-error outcomes stay distinct.
|
|
151
|
+
|
|
152
|
+
`Fleet#plan_backup` and `Fleet#plan_tftp_backup` select devices from one validated Netdisco snapshot. Pass the returned plan to `backup_all(plan:)` or `tftp_backup_all(plan:)` so the preview and execution use exactly the same devices. Execution rejects a plan whose tasks or skip results no longer match its inventory. `Netdisco::Worker` runs each selected device independently; an exception or result-callback failure on one device does not stop the others. `batch.summary` reports total, succeeded, failed, partial, skipped, exact status counts, per-device outcomes, and an overall `status` of `succeeded`, `incomplete`, or `no_devices`. A partial result means the device reported a completed backup but closing its session failed. TFTP `reported_uploaded` only means the device reported an upload; it does not verify a file on the server.
|
|
153
|
+
|
|
154
|
+
Local `backup(path:)` compares the new configuration with the existing file by SHA-256. It reports `:created`, `:changed`, or `:unchanged` through `backup.change`; unchanged files keep their modification time. The `on_change:` callback on `backup_all` runs only after a newly created or changed file is saved, including a saved file whose session later fails to close. `on_start:` and `on_result:` observe each attempted device for either batch method. Callback exceptions are recorded in `batch.callback_errors` without stopping other devices. Each outcome records its start, finish, and duration. TFTP uploads have no `change` value because this library cannot compare the server file; a device-reported upload must not trigger a change notification.
|
|
155
|
+
|
|
156
|
+
Each batch writes a private JSON report by default and returns its path in `batch.report_location`. Set `result_store: Net::Connector::Netdisco::ResultStore::Database.new(repository: YourModel)` when the caller owns a database table; the repository must implement `create!(attributes)` for `batch.summary`. Pass `result_store: nil` to handle persistence elsewhere. Report write failures remain visible as `batch.report_error`, and `batch.success?` becomes false without losing device outcomes.
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
export NETDISCO_URL=https://netdisco.example/netdisco
|
|
160
|
+
export NETDISCO_USERNAME=inventory-reader
|
|
161
|
+
export NETDISCO_PASSWORD='replace-me'
|
|
162
|
+
export NET_CONNECTOR_DEVICE_USERNAME=backup-user
|
|
163
|
+
export NET_CONNECTOR_DEVICE_PASSWORD='replace-me'
|
|
164
|
+
export NET_CONNECTOR_BACKUP_DIRECTORY=/var/backups/network
|
|
165
|
+
export NET_CONNECTOR_CONCURRENCY=4
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
require "net/connector/netdisco"
|
|
170
|
+
|
|
171
|
+
fleet = Net::Connector::Netdisco::Fleet.new
|
|
172
|
+
plan = fleet.plan_backup
|
|
173
|
+
puts plan.selected.map(&:host)
|
|
174
|
+
batch = fleet.backup_all(plan: plan, on_change: ->(outcome) {
|
|
175
|
+
puts "#{outcome.device.host}: #{outcome.backup.change}"
|
|
176
|
+
})
|
|
177
|
+
puts batch.counts
|
|
178
|
+
puts batch.summary.slice(:succeeded, :failed, :partial, :skipped)
|
|
179
|
+
puts batch.report_location
|
|
180
|
+
batch.outcomes.each do |outcome|
|
|
181
|
+
puts "#{outcome.device.source_ip}: #{outcome.status} #{outcome.backup&.path}"
|
|
182
|
+
end
|
|
183
|
+
exit 1 unless batch.success?
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The gem also installs `net-connector-backup`. Its YAML file contains non-secret settings; keep Netdisco and device credentials in environment variables. Environment variables override YAML values. The file is loaded only when `--config FILE` or `NET_CONNECTOR_CONFIG` is set. For example:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
netdisco:
|
|
190
|
+
url: https://netdisco.example/netdisco
|
|
191
|
+
page_size: 500
|
|
192
|
+
backup:
|
|
193
|
+
directory: /var/backups/network
|
|
194
|
+
concurrency: 4
|
|
195
|
+
inventory:
|
|
196
|
+
include_vendors: [h3c, huawei, cisco_ios]
|
|
197
|
+
host_overrides:
|
|
198
|
+
192.0.2.7: h3c_wireless
|
|
199
|
+
ssh:
|
|
200
|
+
host_key_policy: strict
|
|
201
|
+
tftp:
|
|
202
|
+
server: 192.0.2.10
|
|
203
|
+
vrfs:
|
|
204
|
+
cisco_nxos: management
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
net-connector-backup --config config.yml --show-config
|
|
209
|
+
net-connector-backup --config config.yml --plan --host 192.0.2.7
|
|
210
|
+
net-connector-backup --config config.yml --host 192.0.2.7
|
|
211
|
+
net-connector-backup --config config.yml --tftp --plan
|
|
212
|
+
net-connector-backup --config config.yml --tftp --all
|
|
213
|
+
net-connector-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`--plan` fetches and validates inventory without logging into devices. `--host` selects one management IP from that inventory. `--tftp` selects up to five devices per vendor by default; `--all` selects every ready device. Local backups select all ready devices unless `--limit-per-vendor` or `backup.limit_per_vendor` is set. `--show-config` prints effective non-secret settings and does not contact Netdisco. The CLI rejects unknown YAML keys, Ruby object tags, and secrets in the YAML schema. `--export IP` reads an existing local `<IP>.txt` backup (or a unique legacy `<device name>-<IP>.txt` file) without contacting Netdisco or the device; it writes the exact contents to stdout, or atomically creates a mode `0600` file when `--output` is set. Treat exported configurations as sensitive.
|
|
217
|
+
|
|
218
|
+
The CLI prints JSON for plans and batch summaries. Exit status is `0` when a nonempty inventory has only successful outcomes, `1` when the inventory is empty or any outcome was skipped, partial, or failed, and `2` for an inventory or configuration error. A `--host` address absent from the inventory is an error with status `2`. A `--host` run can return `1` when its selected backup succeeds because other inventory rows are reported as filtered; inspect `succeeded`, `skipped`, and per-device `status` in the JSON summary.
|
|
219
|
+
|
|
220
|
+
For a small live trial, run [examples/netdisco_backup.rb](examples/netdisco_backup.rb). It fetches a validated inventory snapshot and backs up at most three ready devices per mapped vendor by default. Set `NET_CONNECTOR_SAMPLE_PER_VENDOR` to an integer from 1 to 5 to change the sample size. Results and a per-device `summary.json` are written under a unique `examples/backups/<UTC timestamp>-<suffix>/` directory; that directory is Git-ignored. The example uses the same environment variables listed below.
|
|
221
|
+
|
|
222
|
+
For device-initiated TFTP uploads, run [examples/netdisco_tftp_backup.rb](examples/netdisco_tftp_backup.rb) with `NETDISCO_URL`, `TFTP_HOST`, and the credential environment variables below. It samples at most five devices per vendor by default; set `NET_CONNECTOR_ALL=1` to select every ready inventory device. Full runs use 50 simultaneous tasks by default; `NET_CONNECTOR_CONCURRENCY` can override this. The full-run plan is saved as `plan.json`. Remote filenames are unique `<device name>-<IP>.cfg` (`.tgz` for Radware, `.dat` for Hillstone). PAN-OS uses its fixed `running-config.xml` filename: additional Palo Alto devices are reported as `remote_filename_collision` rather than overwriting another backup. PAN-OS requires a positive `Sent ... bytes` response to report upload success. H3C and H3C wireless read `display startup` to find the saved startup file; override the source with `NET_CONNECTOR_H3C_TFTP_SOURCE_FILE` or `NET_CONNECTOR_H3C_WIRELESS_TFTP_SOURCE_FILE`. Huawei defaults to `flash:/startup.cfg` and accepts `NET_CONNECTOR_HUAWEI_TFTP_SOURCE_FILE`. Nexus 9000 uses `management` by default and Hillstone uses `mgt-vr`; set `NET_CONNECTOR_TFTP_VRFS` to a JSON map such as `{"cisco_nxos":"backup","hillstone":"mgt-vr"}` to override either one. Hillstone appends its unique `.dat` filename after the device `vrouter` argument. When calling a Hillstone connector directly without `path:`, the device generates its own filename and the return value reports that filename. `reported_uploaded` means the device reported success. Small runs attempt a server readback; full runs skip per-file readback and mark these files unverified. Run `ruby examples/review_tftp_backup.rb <batch directory>` to keep the original summary and review remaining failures from the session logs. Each `logs/<IP>.log` contains the session events and a final backup result; at debug level it also includes the sanitized login and device output in that same file. Per-device outcomes, incremental `events.jsonl`, and logs are written privately under a unique `examples/backups/<UTC timestamp>-<suffix>/` directory.
|
|
223
|
+
|
|
224
|
+
Local backups are written to `<backup directory>/<IP>.txt`, using the normalized management IP; `:` in IPv6 addresses becomes `_`. Device renames preserve the filename and comparison baseline. If the canonical file is absent, a unique legacy `<device name>-<IP>.txt` file supplies the comparison baseline and remains untouched. Multiple legacy matches fail explicitly; review them and place the verified current configuration at the canonical path. Canonical files take precedence, and symlinks or non-regular files are rejected. Inventory names remain in result metadata and TFTP filenames. Files are atomically replaced with mode `0600`. The directory is created with mode `0700` if absent. A batch runs in the current process; schedule it with your job runner or cron if you need recurring or durable work. It does not retry a failed device command.
|
|
225
|
+
|
|
226
|
+
| Environment variable | Default | Purpose |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| `NETDISCO_URL` | required | Netdisco base URL, including any tenant path |
|
|
229
|
+
| `NET_CONNECTOR_CONFIG` | unset | Explicit non-secret YAML settings file for the CLI |
|
|
230
|
+
| `NETDISCO_USERNAME`, `NETDISCO_PASSWORD` | required unless API key is supplied | Inventory API login |
|
|
231
|
+
| `NETDISCO_API_KEY` | unset | Use an existing API key instead of login |
|
|
232
|
+
| `NETDISCO_PAGE_SIZE` | `500` | Device inventory page size |
|
|
233
|
+
| `NET_CONNECTOR_DEVICE_USERNAME`, `NET_CONNECTOR_DEVICE_PASSWORD` | unset | Device login defaults |
|
|
234
|
+
| `NET_CONNECTOR_<VENDOR>_USERNAME`, `NET_CONNECTOR_<VENDOR>_PASSWORD` | unset | Override credentials for one connector key, such as `CISCO_IOS` |
|
|
235
|
+
| `NET_CONNECTOR_BACKUP_DIRECTORY` | `./backups` | Backup destination and default batch-report directory |
|
|
236
|
+
| `NET_CONNECTOR_CONCURRENCY` | `4` | Maximum simultaneous device backups, from 1 to 50 |
|
|
237
|
+
| `NET_CONNECTOR_INCLUDE_HOSTS`, `NET_CONNECTOR_EXCLUDE_HOSTS` | unset | Comma-separated management IP filters |
|
|
238
|
+
| `NET_CONNECTOR_INCLUDE_VENDORS` | unset | Comma-separated connector keys to include |
|
|
239
|
+
| `NET_CONNECTOR_VENDOR_OVERRIDES` | `{}` | JSON object mapping a Netdisco vendor label to a connector key |
|
|
240
|
+
| `NET_CONNECTOR_HOST_OVERRIDES` | `{}` | JSON object mapping a management IP to a connector key |
|
|
241
|
+
| `NET_CONNECTOR_DEVICE_RULES` | `[]` | JSON array of mapping rules with `vendor`, optional `os` or `model_prefix`, and `connector` |
|
|
242
|
+
| `NET_CONNECTOR_PROTOCOL` | `ssh` | Default device protocol; per-vendor override is available |
|
|
243
|
+
| `NET_CONNECTOR_KNOWN_HOSTS`, `NET_CONNECTOR_HOST_KEY_POLICY` | system hosts, `strict` | SSH host-key settings |
|
|
244
|
+
| `NET_CONNECTOR_LOG_DIRECTORY` | unset | Optional per-device session log directory |
|
|
245
|
+
| `NET_CONNECTOR_LOG_LEVEL` | `info` (`debug` in TFTP example) | `debug`, `info`, `warn`, or `error` event detail |
|
|
246
|
+
| `NET_CONNECTOR_TFTP_VRFS` | `{}` | JSON map of NX-OS and Hillstone VRF names for the TFTP example |
|
|
247
|
+
|
|
248
|
+
Mapping rules are evaluated before vendor-label overrides and built-in rules. A specific rule can distinguish models that share a vendor label:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
export NET_CONNECTOR_DEVICE_RULES='[{"vendor":"Cisco","model_prefix":"N9K","connector":"cisco_nxos"}]'
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The environment is read when a `Settings` object builds its client or rules, and device credentials are read for each backup task. A new batch can therefore pick up rotated credentials. For per-device secrets managed outside the environment, inject a resolver: `Fleet.new(credentials: ->(device) { { username: "...", password: "..." } })`. To inspect association without connecting, call `fleet.devices`; a ready item can instantiate its connector with `device.connector(username: "...", password: "...")`.
|
|
255
|
+
|
|
256
|
+
## Development
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
bundle install
|
|
260
|
+
script/ci
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The same command runs in CI on Linux and macOS with Ruby 3.2, 3.3, 3.4 and 4.0.
|
|
264
|
+
It scans source and available Git history for sensitive data, runs Ruby and
|
|
265
|
+
workflow lint plus the complete test suite, and verifies the built gem through
|
|
266
|
+
isolated installation and a real local PTY session. No network device is needed.
|
|
267
|
+
The first run downloads checksum-pinned Gitleaks and actionlint binaries.
|
|
268
|
+
|
|
269
|
+
`bundle exec rake test` runs tests, `bundle exec rake lint` checks Ruby code,
|
|
270
|
+
and `bundle exec rake security:check` scans source and history. The full
|
|
271
|
+
pre-release check is also available as `bundle exec rake release:check`.
|
|
272
|
+
Builds and redacted scan reports go under ignored `tmp/` directories.
|
|
273
|
+
|
|
274
|
+
Keep real credentials in environment variables and local configuration outside
|
|
275
|
+
version control. See [verification and sensitive-data policy](docs/VERIFICATION.md)
|
|
276
|
+
for scan coverage, dependency policy and `.gitignore`, and
|
|
277
|
+
[release instructions](docs/RELEASING.md) for local and GitHub Actions publishing.
|
data/docs/RELEASING.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 发布
|
|
2
|
+
|
|
3
|
+
发布脚本参考 `expect` 项目的同一产物验证流程。本地复用已有 `gem` / `gh`
|
|
4
|
+
登录;GitHub Actions 使用自己的令牌。发布前先执行:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
bundle install
|
|
8
|
+
bundle exec rake release:check
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
此命令不会上传,详细检查范围见 [VERIFICATION.md](VERIFICATION.md)。
|
|
12
|
+
|
|
13
|
+
## 准备版本
|
|
14
|
+
|
|
15
|
+
1. 在 `lib/net/connector/version.rb` 设置新的稳定 `X.Y.Z` 版本。
|
|
16
|
+
2. 将 CHANGELOG 的 `Unreleased` 内容移入对应的 `## X.Y.Z` 或
|
|
17
|
+
`## X.Y.Z - YYYY-MM-DD` 段;版本说明不能为空。
|
|
18
|
+
3. 提交最终源码,确认工作区干净,并将提交推送到目标 GitHub 仓库的 `main`。
|
|
19
|
+
4. 执行 dry run,保留生成的 gem 和 SHA-256,审核后选择下面一种发布方式。
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
ruby script/release.rb --dry-run
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
dry run 执行本地检查,创建独占的 `tmp/release/<version>/candidate-*/` 目录,
|
|
26
|
+
保存经过验证的 gem、`SHA256SUMS` 和发布说明,不访问发布 API 或上传文件。
|
|
27
|
+
它允许未提交的工作区用于预检,但仍要求版本与发布说明已经整理完成。
|
|
28
|
+
正式发布额外要求干净且未变化的 Git 提交。
|
|
29
|
+
|
|
30
|
+
当前存在尚未归档的 `Unreleased` 时,发布脚本(包括 dry run)会拒绝继续;
|
|
31
|
+
需要先整理版本。日常使用 `release:check` 不受此限制。
|
|
32
|
+
|
|
33
|
+
## 本地发布
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
ruby script/release.rb
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
默认从 `GITHUB_REPOSITORY` 或 GitHub `origin` 推导仓库;也可用
|
|
40
|
+
`--repository OWNER/REPO` 指定。脚本检查目标 `main` 包含本次提交,已有同名
|
|
41
|
+
标签必须指向同一提交;随后创建或复用 GitHub Release,再上传 RubyGems。
|
|
42
|
+
首次发布可以由脚本创建版本标签。
|
|
43
|
+
|
|
44
|
+
如果已经审核过某个包,使用其真实路径替换下例占位路径:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
ruby script/release.rb --artifact /path/to/verified/net-connector-X.Y.Z.gem
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
此模式不重新构建,仍扫描源码与历史,验证包的版本、元数据、文件字节、执行位
|
|
51
|
+
及敏感信息,然后复制到本次独占目录。也支持与 `--dry-run` 组合。
|
|
52
|
+
`--rubygems-only` 只发布 RubyGems,仍检查本地提交和产物,不需要 GitHub 登录。
|
|
53
|
+
|
|
54
|
+
本地发布使用已有的 `gem` 登录和 `gh` 登录。请在终端配置凭据,避免把凭据
|
|
55
|
+
写进参数、源码或发布文档。脚本不修改本地认证配置。
|
|
56
|
+
|
|
57
|
+
## GitHub Actions 发布
|
|
58
|
+
|
|
59
|
+
仓库中配置 `RUBYGEMS_API_KEY` Actions Secret,并赋予发布此 gem 所需的权限。
|
|
60
|
+
Runner 不继承本机 RubyGems 登录。GitHub 操作使用工作流自带的 token。
|
|
61
|
+
|
|
62
|
+
提交和推送版本修改后,创建并推送对应的 `vX.Y.Z` 标签,在该标签上手动运行
|
|
63
|
+
`Release` 工作流。只允许标签与 gem 版本一致且说明已归档的发布。
|
|
64
|
+
工作流先复用完整 CI 矩阵,全部通过后下载 Ubuntu / Ruby 4.0 验证的同一个
|
|
65
|
+
gem,再执行 `--artifact`;发布阶段不重新构建。
|
|
66
|
+
|
|
67
|
+
普通 push、PR 和标签推送只触发 CI。Release 工作流仅手动触发,避免本地
|
|
68
|
+
发布创建标签时又自动开始第二次发布。
|
|
69
|
+
|
|
70
|
+
## 失败后重试
|
|
71
|
+
|
|
72
|
+
保留日志中显示的原始候选 gem,使用 `--artifact` 重试。不要在同一版本下
|
|
73
|
+
重新构建不同字节的包。脚本校验已有 GitHub 附件,并从 GitHub / RubyGems
|
|
74
|
+
下载实际文件核对 SHA-256;遇到同版本不同字节或已撤回的 RubyGems 版本会拒绝。
|
|
75
|
+
|
|
76
|
+
GitHub 草稿可复用,缺失附件可补齐。若上传中断留下未完成附件,先确认没有
|
|
77
|
+
仍在运行的上传,移除该附件,再用同一个 gem 重试。已完成但不同字节的
|
|
78
|
+
附件不会被覆盖。RubyGems 成功而响应丢失时,重试会读取版本信息并核对原包。
|
|
79
|
+
|
|
80
|
+
源码、历史或包中发现敏感数据时,先处理问题并重新审核;已经公开的凭据
|
|
81
|
+
应在对应系统轮换。脚本不会跳过扫描,也不会自动清理历史或撤回远端版本。
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 验证、依赖与敏感数据
|
|
2
|
+
|
|
3
|
+
在项目根目录执行:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bundle install
|
|
7
|
+
script/ci
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`bundle exec rake ci` 和 `bundle exec rake release:check` 使用同一套检查。
|
|
11
|
+
`release:check` 可以在仍有 `Unreleased` 变更时运行,不执行发布。
|
|
12
|
+
|
|
13
|
+
## 检查范围
|
|
14
|
+
|
|
15
|
+
| 步骤 | 内容 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `security:check` | 扫描待提交源码和可用的完整 Git 历史;拒绝混入源码的本地凭据、配置和产物 |
|
|
18
|
+
| `lint` | 对库、脚本、示例、测试、Gemfile、gemspec 和 Rakefile 执行 RuboCop |
|
|
19
|
+
| `lint:workflows` | 用 actionlint 校验 GitHub Actions 工作流 |
|
|
20
|
+
| `test` | 执行全部 Minitest;敏感信息和发布测试使用临时文件、临时仓库与模拟远端响应 |
|
|
21
|
+
| `package:verify` | 构建 gem,检查元数据、文件白名单、源文件字节和执行位,扫描解包内容及元数据,再进行隔离安装 |
|
|
22
|
+
|
|
23
|
+
隔离安装清除当前 Bundler 和 Ruby 注入变量,分别验证普通 `gem install`
|
|
24
|
+
和只有 `net-connector` 依赖的最小 Bundler 应用。烟测加载全部厂商,使用本地
|
|
25
|
+
PTY 子进程采集配置,读取包内 TextFSM 模板,并检查 CLI。它不连接网络设备,
|
|
26
|
+
也不证明现场设备协议或真实发布服务已验收。
|
|
27
|
+
|
|
28
|
+
CI 矩阵为 Ubuntu 24.04 / macOS 15 × Ruby 3.2、3.3、3.4、4.0。
|
|
29
|
+
GitHub Actions 固定提交 SHA;Gitleaks 与 actionlint 固定版本和各平台归档
|
|
30
|
+
SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/`。
|
|
31
|
+
已有工具归档和可执行文件也会再次校验。初次安装依赖和下载工具需要联网;
|
|
32
|
+
隔离安装复用本次 Bundler 安装所得的 gem 缓存。Bundler 自身优先使用缓存
|
|
33
|
+
安装;若它是 Ruby 随附且没有缓存的默认 gem,则直接加载该精确版本。
|
|
34
|
+
|
|
35
|
+
## 依赖与打包
|
|
36
|
+
|
|
37
|
+
运行依赖写在 `net-connector.gemspec`,包括直接使用的、可能从 Ruby 默认
|
|
38
|
+
安装中拆出的标准库 gem。开发工具只写在 Gemfile,不进入运行依赖。
|
|
39
|
+
`expect-pty` 使用 `~> 0.3.1`;开发用 `parallel` 保持 1.x,以支持 Ruby 3.2。
|
|
40
|
+
|
|
41
|
+
本项目是库,`Gemfile.lock` 仅作本地开发记录并被忽略;各 Ruby 版本的 CI
|
|
42
|
+
分别解析兼容依赖。应用使用者应在自己的应用中提交 lockfile。测试和打包
|
|
43
|
+
脚本通过隔离安装检查运行依赖,避免依赖开发环境里偶然存在的 gem。
|
|
44
|
+
|
|
45
|
+
gem 只收录库代码、TextFSM 模板、CLI、架构/验证/发布文档、README、LICENSE 和
|
|
46
|
+
CHANGELOG。测试、示例、发布工具、工作流、本地评审快照、配置和备份不进入包。
|
|
47
|
+
更改 gemspec 后,实际归档仍须通过独立的文件白名单检查。
|
|
48
|
+
|
|
49
|
+
## 敏感数据
|
|
50
|
+
|
|
51
|
+
Gitleaks 默认规则之外,还检查网络设备密码/SNMP community 配置、环境变量
|
|
52
|
+
中的字面量凭据和私有 IPv4 地址。公开示例使用 `replace-me`、环境变量引用和
|
|
53
|
+
文档地址(例如 `192.0.2.1`)。代码和测试不享受目录级豁免;内联
|
|
54
|
+
`gitleaks:allow` 或 `.gitleaksignore` 也不能绕过这里的扫描。
|
|
55
|
+
|
|
56
|
+
有 Git 仓库时,源码检查覆盖已跟踪文件和未忽略的新文件,并以 `--all`
|
|
57
|
+
检查本地可用引用的历史。CI 使用 `fetch-depth: 0`,浅克隆会被拒绝。
|
|
58
|
+
没有 Git 元数据或没有提交时,只执行源码检查并明确报告历史检查不可用;
|
|
59
|
+
临时测试仓库中已删除敏感数据的提交也必须能被历史扫描检出。
|
|
60
|
+
|
|
61
|
+
扫描失败会停止后续构建或发布。日志和 `tmp/security/*.json` 报告仅记录规则、
|
|
62
|
+
文件与行号,不记录匹配文本或秘密值;报告权限为 `0600`。最终 gem 和
|
|
63
|
+
`--artifact` 重试同样接受检查。扫描器异常、缺失报告也按失败处理。
|
|
64
|
+
|
|
65
|
+
`.gitignore` 排除本地环境文件、配置、凭据、SSH 密钥、设备备份、日志、报告、
|
|
66
|
+
构建包、依赖缓存与编辑器文件;保留 `.env.example`、厂商代码与工作流。
|
|
67
|
+
忽略规则不会移除已经跟踪的文件,源码检查会拒绝已跟踪的禁入路径。
|
|
68
|
+
已有 Git 暂存区仍需包含最终脱敏后的修改,避免提交旧的暂存内容。
|
|
69
|
+
|
|
70
|
+
发现真实敏感数据时,在源码或示例中替换为占位值,并检查历史及已分发的包。
|
|
71
|
+
若凭据已经泄露,需在对应系统撤销或轮换;仅修改示例不能使旧凭据失效。
|
|
72
|
+
扫描工具不会自动改写 Git 历史或真实设备备份。规则用于拦截常见泄露,
|
|
73
|
+
发布前仍需人工确认设备名称、拓扑和业务配置等上下文信息是否适合公开。
|