net-connector 0.16.1 → 0.17.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/net/connector/{netdisco → inventory}/address_set.rb +1 -1
- data/lib/net/connector/inventory/filters.rb +50 -0
- data/lib/net/connector/inventory/selection.rb +96 -0
- data/lib/net/connector/netdisco/cli/options.rb +9 -17
- data/lib/net/connector/netdisco/config/defaults.rb +2 -1
- data/lib/net/connector/netdisco/config/initializer.rb +2 -1
- data/lib/net/connector/netdisco/config.rb +3 -0
- data/lib/net/connector/netdisco/device.rb +1 -1
- data/lib/net/connector/netdisco/rules.rb +8 -42
- data/lib/net/connector/netdisco/settings.rb +4 -0
- data/lib/net/connector/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 233cb61903727cc01241fcae91e4e540a8bae1462b01210e73aa246fd1a906c9
|
|
4
|
+
data.tar.gz: 923cdca12fc7c38841743618a0d22527536bee646de7687c26480afbc3265a4a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3f2a794edb16faf5429967c4a25ee53a949fef194883cb5aefdc0f9a1a9968ccdf8a539e2f059a4a7d3f28d1e299ab1481c5d66823d555324bb11e2ae10ee0c
|
|
7
|
+
data.tar.gz: a3ca7a0f4bee7e40861a700063c448f5ac918bf09d7d5b5c87510a664eb024c3fd98cee3ae9a27516d63b59ee8c7aa6af7000231d74acaf24405c2852a7b423f
|
data/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "selection"
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module Connector
|
|
8
|
+
module Inventory
|
|
9
|
+
# 脚本共享筛选词汇和参数;配置来源及优先级仍由宿主决定。
|
|
10
|
+
module Filters
|
|
11
|
+
LISTS = {
|
|
12
|
+
"hosts" => :include_hosts, "exclude_hosts" => :exclude_hosts,
|
|
13
|
+
"names" => :include_names, "exclude_names" => :exclude_names,
|
|
14
|
+
"vendors" => :include_vendors, "exclude_vendors" => :exclude_vendors,
|
|
15
|
+
"models" => :include_models, "exclude_models" => :exclude_models
|
|
16
|
+
}.freeze
|
|
17
|
+
MODES = %w[name_match model_match].freeze
|
|
18
|
+
FIELDS = (LISTS.keys + MODES).freeze
|
|
19
|
+
FLAGS = {
|
|
20
|
+
"--hosts LIST" => "hosts", "--exclude-hosts LIST" => "exclude_hosts",
|
|
21
|
+
"--name LIST" => "names", "--exclude-name LIST" => "exclude_names",
|
|
22
|
+
"--vendor LIST" => "vendors", "--exclude-vendor LIST" => "exclude_vendors",
|
|
23
|
+
"--model LIST" => "models", "--exclude-model LIST" => "exclude_models"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def self.selection(values)
|
|
27
|
+
arguments = LISTS.to_h { |key, argument| [argument, values.fetch(key, [])] }
|
|
28
|
+
MODES.each { |key| arguments[key.to_sym] = values.fetch(key, :contains) }
|
|
29
|
+
Selection.new(**arguments)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 每个回调只交付本次参数;追加或覆盖由配置适配器显式处理。
|
|
33
|
+
def self.add_options(parser, single_host: true, &accept)
|
|
34
|
+
flags = single_host ? {"--host IP" => "hosts"}.merge(FLAGS) : FLAGS
|
|
35
|
+
flags.each do |flag, key|
|
|
36
|
+
parser.on(flag, "筛选已有清单,同类条件取并集,排除优先") do |value|
|
|
37
|
+
items = value.split(",", -1).map(&:strip)
|
|
38
|
+
raise OptionParser::InvalidArgument, "filter list contains empty items" if items.empty? || items.any?(&:empty?)
|
|
39
|
+
|
|
40
|
+
accept.call(key, items)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
MODES.each do |key|
|
|
44
|
+
parser.on("--#{key.tr("_", "-")} MODE", %w[contains prefix glob], "匹配方式 contains / prefix / glob") { |value| accept.call(key, value) }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "address_set"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Inventory
|
|
8
|
+
# 只筛选已有设备,不识别厂商、不发现地址、不建立连接。
|
|
9
|
+
class Selection
|
|
10
|
+
Decision = Data.define(:reasons) do
|
|
11
|
+
def initialize(reasons:)
|
|
12
|
+
super(reasons: reasons.dup.freeze)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def selected? = reasons.empty?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(include_hosts: [], exclude_hosts: [], include_names: [], exclude_names: [],
|
|
19
|
+
include_vendors: [], exclude_vendors: [], include_models: [], exclude_models: [],
|
|
20
|
+
name_match: :contains, model_match: :contains)
|
|
21
|
+
@include_hosts, @exclude_hosts = AddressSet.new(include_hosts), AddressSet.new(exclude_hosts)
|
|
22
|
+
@names = [text_list(include_names), text_list(exclude_names), mode(name_match)].freeze
|
|
23
|
+
@models = [text_list(include_models), text_list(exclude_models), mode(model_match)].freeze
|
|
24
|
+
@vendors = [vendor_list(include_vendors), vendor_list(exclude_vendors)].freeze
|
|
25
|
+
freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 同字段包含取并集,不同字段取交集;排除优先,原因不携带设备正文。
|
|
29
|
+
def evaluate(host:, vendor: nil, name: nil, model: nil)
|
|
30
|
+
reasons = []
|
|
31
|
+
if @exclude_hosts.include?(host)
|
|
32
|
+
reasons << :host_excluded
|
|
33
|
+
elsif !@include_hosts.empty? && !@include_hosts.include?(host)
|
|
34
|
+
reasons << :host_not_included
|
|
35
|
+
end
|
|
36
|
+
vendor = vendor.to_s
|
|
37
|
+
if @vendors.last.include?(vendor)
|
|
38
|
+
reasons << :vendor_excluded
|
|
39
|
+
elsif !@vendors.first.empty? && !@vendors.first.include?(vendor)
|
|
40
|
+
reasons << :vendor_not_included
|
|
41
|
+
end
|
|
42
|
+
reasons << text_reason(:name, name, @names)
|
|
43
|
+
reasons << text_reason(:model, model, @models)
|
|
44
|
+
Decision.new(reasons: reasons.compact)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def selected?(host, vendor = nil, name: nil, model: nil)
|
|
48
|
+
evaluate(host: host, vendor: vendor, name: name, model: model).selected?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inspect = "#<#{self.class}>"
|
|
52
|
+
alias to_s inspect
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def mode(value)
|
|
57
|
+
unless (value.is_a?(String) || value.is_a?(Symbol)) && %w[contains prefix glob].include?(value.to_s)
|
|
58
|
+
raise ArgumentError, "match mode must be contains, prefix or glob"
|
|
59
|
+
end
|
|
60
|
+
value.to_sym
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def text_list(values)
|
|
64
|
+
unless values.is_a?(Array) && values.all? { |v| v.is_a?(String) && v.valid_encoding? && !v.strip.empty? && !/[\x00-\x1f\x7f]/.match?(v) }
|
|
65
|
+
raise ArgumentError, "filters must be arrays of nonempty text"
|
|
66
|
+
end
|
|
67
|
+
values.map { |v| v.downcase.freeze }.uniq.freeze
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def vendor_list(values)
|
|
71
|
+
unless values.is_a?(Array) && values.all? { |v| v.is_a?(String) || v.is_a?(Symbol) }
|
|
72
|
+
raise ArgumentError, "vendors must be strings or symbols"
|
|
73
|
+
end
|
|
74
|
+
text_list(values.map(&:to_s))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def text_reason(field, value, rules)
|
|
78
|
+
includes, excludes, match = rules
|
|
79
|
+
value = value&.to_s&.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")&.downcase
|
|
80
|
+
return :"#{field}_excluded" if value && excludes.any? { |pattern| matches?(pattern, value, match) }
|
|
81
|
+
return :"#{field}_not_included" if !includes.empty? && (!value || includes.none? { |pattern| matches?(pattern, value, match) })
|
|
82
|
+
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def matches?(pattern, value, match)
|
|
87
|
+
case match
|
|
88
|
+
when :contains then value.include?(pattern)
|
|
89
|
+
when :prefix then value.start_with?(pattern)
|
|
90
|
+
when :glob then File.fnmatch?(pattern, value)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -6,6 +6,7 @@ require_relative "../planner"
|
|
|
6
6
|
require_relative "../config"
|
|
7
7
|
require_relative "../../config"
|
|
8
8
|
require_relative "help"
|
|
9
|
+
require_relative "../../inventory/filters"
|
|
9
10
|
|
|
10
11
|
module Net
|
|
11
12
|
module Connector
|
|
@@ -208,25 +209,16 @@ module Net
|
|
|
208
209
|
|
|
209
210
|
# 同类 CLI 筛选替换 YAML;重复参数在本次调用内合并,绝不把范围展开为地址列表。
|
|
210
211
|
def self.add_filters(opts, values)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
opts.on(flag, description) do |value|
|
|
220
|
-
items = value.split(",", -1).map(&:strip)
|
|
221
|
-
raise OptionParser::InvalidArgument if items.empty? || items.any?(&:empty?)
|
|
222
|
-
|
|
223
|
-
previous = values[:environment][key]&.split(",") || []
|
|
224
|
-
values[:environment][key] = (previous + items).uniq.join(",")
|
|
212
|
+
Net::Connector::Inventory::Filters.add_options(opts, single_host: false) do |key, value|
|
|
213
|
+
field = Net::Connector::Inventory::Filters::LISTS.fetch(key, key).to_s
|
|
214
|
+
environment_key = "NC_#{field.upcase}"
|
|
215
|
+
if value.is_a?(Array)
|
|
216
|
+
previous = values[:environment][environment_key]&.split(",") || []
|
|
217
|
+
values[:environment][environment_key] = (previous + value).uniq.join(",")
|
|
218
|
+
else
|
|
219
|
+
values[:environment][environment_key] = value
|
|
225
220
|
end
|
|
226
221
|
end
|
|
227
|
-
opts.on("--name-match MODE", %w[contains prefix glob], "名称匹配方式:contains / prefix / glob") do |value|
|
|
228
|
-
values[:environment]["NC_NAME_MATCH"] = value
|
|
229
|
-
end
|
|
230
222
|
opts.on("--host IP", "选择单台设备,可重复") do |value|
|
|
231
223
|
raise OptionParser::InvalidArgument if value.empty? || value.match?(/[\/,]/)
|
|
232
224
|
|
|
@@ -15,7 +15,8 @@ module Net
|
|
|
15
15
|
"cache_directory" => "./.cache/net-connector/inventory", "inventory_timeout" => 300,
|
|
16
16
|
"page_size" => 500, "allow_insecure_http" => false },
|
|
17
17
|
"inventory" => { "include_hosts" => [], "exclude_hosts" => [], "include_names" => [], "exclude_names" => [],
|
|
18
|
-
"include_vendors" => [], "exclude_vendors" => [], "name_match" => "contains"
|
|
18
|
+
"include_vendors" => [], "exclude_vendors" => [], "name_match" => "contains",
|
|
19
|
+
"include_models" => [], "exclude_models" => [], "model_match" => "contains" },
|
|
19
20
|
"ssh" => { "protocol" => "ssh", "host_key_policy" => "accept_new", "log_level" => "info" },
|
|
20
21
|
"backup" => { "directory" => directory || "./backups", "parallel" => 4 }
|
|
21
22
|
}
|
|
@@ -102,10 +102,11 @@ module Net
|
|
|
102
102
|
if document.dig("netdisco", "source") == "http"
|
|
103
103
|
field(document, "netdisco", "url", "清单 URL", mode.nil? ? "https://netdisco.example.invalid" : nil)
|
|
104
104
|
end
|
|
105
|
-
%w[include_hosts exclude_hosts include_names exclude_names].each do |key|
|
|
105
|
+
%w[include_hosts exclude_hosts include_names exclude_names include_models exclude_models].each do |key|
|
|
106
106
|
field(document, "inventory", key, key, [], list: true)
|
|
107
107
|
end
|
|
108
108
|
field(document, "inventory", "name_match", "名称匹配 contains/prefix/glob", "contains")
|
|
109
|
+
field(document, "inventory", "model_match", "型号匹配 contains/prefix/glob", "contains")
|
|
109
110
|
%w[include_vendors exclude_vendors].each { |key| field(document, "inventory", key, key, [], list: true) }
|
|
110
111
|
if document.key?("topology")
|
|
111
112
|
field(document, "topology", "parallel", "拓扑并发数 1..500", inherited_parallel)
|
|
@@ -45,6 +45,9 @@ module Net
|
|
|
45
45
|
"include_names" => ["NC_INCLUDE_NAMES", :list],
|
|
46
46
|
"exclude_names" => ["NC_EXCLUDE_NAMES", :list],
|
|
47
47
|
"name_match" => ["NC_NAME_MATCH", :string],
|
|
48
|
+
"include_models" => ["NC_INCLUDE_MODELS", :list],
|
|
49
|
+
"exclude_models" => ["NC_EXCLUDE_MODELS", :list],
|
|
50
|
+
"model_match" => ["NC_MODEL_MATCH", :string],
|
|
48
51
|
"exclude_vendors" => ["NC_EXCLUDE_VENDORS", :list],
|
|
49
52
|
"include_vendors" => ["NC_INCLUDE_VENDORS", :list],
|
|
50
53
|
"vendor_overrides" => ["NC_VENDOR_OVERRIDES", :json],
|
|
@@ -38,7 +38,7 @@ module Net
|
|
|
38
38
|
end
|
|
39
39
|
vendor = rules.resolve(row)
|
|
40
40
|
name = [row["name"], row["dns"]].find { |value| value && !value.strip.empty? }
|
|
41
|
-
issue ||= :filtered unless rules.selected?(host, vendor, name: name&.strip)
|
|
41
|
+
issue ||= :filtered unless rules.selected?(host, vendor, name: name&.strip, model: row["model"])
|
|
42
42
|
issue ||= :unsupported_vendor unless vendor
|
|
43
43
|
new(host: host, source_ip: source_ip, name: name&.strip, vendor: vendor,
|
|
44
44
|
source_vendor: row["vendor"], model: row["model"], os: row["os"],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "ipaddr"
|
|
4
4
|
require_relative "../../connector"
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "../inventory/selection"
|
|
6
6
|
|
|
7
7
|
module Net
|
|
8
8
|
module Connector
|
|
@@ -12,17 +12,11 @@ module Net
|
|
|
12
12
|
# 校验并保存清单过滤和厂商映射规则。
|
|
13
13
|
def initialize(include_hosts: [], exclude_hosts: [], include_vendors: [], vendor_overrides: {},
|
|
14
14
|
host_overrides: {}, mappings: [], include_names: [], exclude_names: [],
|
|
15
|
-
name_match: :contains, exclude_vendors: [])
|
|
16
|
-
@
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@include_names = name_list(include_names)
|
|
21
|
-
@exclude_names = name_list(exclude_names)
|
|
22
|
-
unless (name_match.is_a?(String) || name_match.is_a?(Symbol)) && %w[contains prefix glob].include?(name_match.to_s)
|
|
23
|
-
raise ArgumentError, "name_match must be contains, prefix or glob"
|
|
24
|
-
end
|
|
25
|
-
@name_match = name_match.to_sym
|
|
15
|
+
name_match: :contains, exclude_vendors: [], include_models: [], exclude_models: [], model_match: :contains)
|
|
16
|
+
@selection = Net::Connector::Inventory::Selection.new(include_hosts: include_hosts, exclude_hosts: exclude_hosts,
|
|
17
|
+
include_names: include_names, exclude_names: exclude_names, name_match: name_match,
|
|
18
|
+
include_vendors: vendor_list(include_vendors), exclude_vendors: vendor_list(exclude_vendors),
|
|
19
|
+
include_models: include_models, exclude_models: exclude_models, model_match: model_match)
|
|
26
20
|
@vendor_overrides = vendor_mapping(vendor_overrides)
|
|
27
21
|
@host_overrides = host_mapping(host_overrides)
|
|
28
22
|
@mappings = device_mappings(mappings)
|
|
@@ -153,19 +147,8 @@ module Net
|
|
|
153
147
|
end
|
|
154
148
|
|
|
155
149
|
# 判断设备是否通过地址和厂商过滤。
|
|
156
|
-
def selected?(host, vendor, name: nil)
|
|
157
|
-
|
|
158
|
-
return false if !@include_hosts.empty? && !@include_hosts.include?(host)
|
|
159
|
-
return false if @exclude_vendors.include?(vendor)
|
|
160
|
-
return false if !@include_vendors.empty? && !@include_vendors.include?(vendor)
|
|
161
|
-
|
|
162
|
-
return true if @include_names.empty? && @exclude_names.empty?
|
|
163
|
-
|
|
164
|
-
value = name.to_s.encode("UTF-8", invalid: :replace, undef: :replace, replace: "").downcase
|
|
165
|
-
return false if @exclude_names.any? { |pattern| name_matches?(pattern, value) }
|
|
166
|
-
return false if !@include_names.empty? && @include_names.none? { |pattern| name_matches?(pattern, value) }
|
|
167
|
-
|
|
168
|
-
true
|
|
150
|
+
def selected?(host, vendor, name: nil, model: nil)
|
|
151
|
+
@selection.selected?(host, vendor, name: name, model: model)
|
|
169
152
|
end
|
|
170
153
|
|
|
171
154
|
private
|
|
@@ -176,23 +159,6 @@ module Net
|
|
|
176
159
|
wireless ? :h3c_wireless : :h3c
|
|
177
160
|
end
|
|
178
161
|
|
|
179
|
-
# 校验名称过滤项为有效非空文本,统一小写并去重冻结。
|
|
180
|
-
def name_list(values)
|
|
181
|
-
unless values.is_a?(Array) && values.all? { |value| value.is_a?(String) && !value.strip.empty? && value.valid_encoding? }
|
|
182
|
-
raise ArgumentError, "name filters must be arrays of nonempty strings"
|
|
183
|
-
end
|
|
184
|
-
values.map { |value| value.downcase.freeze }.uniq.freeze
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# 按包含、前缀或通配符模式匹配已规范化的设备名称。
|
|
188
|
-
def name_matches?(pattern, value)
|
|
189
|
-
case @name_match
|
|
190
|
-
when :contains then value.include?(pattern)
|
|
191
|
-
when :prefix then value.start_with?(pattern)
|
|
192
|
-
when :glob then File.fnmatch?(pattern, value)
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
162
|
# 将设备标签转为便于匹配的小写词串。
|
|
197
163
|
def normalize(value) = value.to_s.downcase.gsub(/[^a-z0-9]+/, " ").strip
|
|
198
164
|
end
|
|
@@ -190,6 +190,8 @@ module Net
|
|
|
190
190
|
exclude_vendors: list("NC_EXCLUDE_VENDORS"),
|
|
191
191
|
include_names: list("NC_INCLUDE_NAMES"), exclude_names: list("NC_EXCLUDE_NAMES"),
|
|
192
192
|
name_match: raw("NC_NAME_MATCH", "contains"),
|
|
193
|
+
include_models: list("NC_INCLUDE_MODELS"), exclude_models: list("NC_EXCLUDE_MODELS"),
|
|
194
|
+
model_match: raw("NC_MODEL_MATCH", "contains"),
|
|
193
195
|
vendor_overrides: json("NC_VENDOR_OVERRIDES", {}),
|
|
194
196
|
host_overrides: json("NC_HOST_OVERRIDES", {}),
|
|
195
197
|
mappings: json("NC_DEVICE_RULES", []))
|
|
@@ -381,6 +383,8 @@ module Net
|
|
|
381
383
|
exclude_vendors: list("NC_EXCLUDE_VENDORS"),
|
|
382
384
|
include_names: list("NC_INCLUDE_NAMES"), exclude_names: list("NC_EXCLUDE_NAMES"),
|
|
383
385
|
name_match: raw("NC_NAME_MATCH", "contains"),
|
|
386
|
+
include_models: list("NC_INCLUDE_MODELS"), exclude_models: list("NC_EXCLUDE_MODELS"),
|
|
387
|
+
model_match: raw("NC_MODEL_MATCH", "contains"),
|
|
384
388
|
vendor_overrides: json("NC_VENDOR_OVERRIDES", {}),
|
|
385
389
|
host_overrides: json("NC_HOST_OVERRIDES", {}),
|
|
386
390
|
device_rules: json("NC_DEVICE_RULES", []) },
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: net-connector
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- net-connector contributors
|
|
@@ -417,8 +417,10 @@ files:
|
|
|
417
417
|
- lib/net/connector/engine/terminal/renderer.rb
|
|
418
418
|
- lib/net/connector/engine/terminal/text.rb
|
|
419
419
|
- lib/net/connector/engine/transports.rb
|
|
420
|
+
- lib/net/connector/inventory/address_set.rb
|
|
421
|
+
- lib/net/connector/inventory/filters.rb
|
|
422
|
+
- lib/net/connector/inventory/selection.rb
|
|
420
423
|
- lib/net/connector/netdisco.rb
|
|
421
|
-
- lib/net/connector/netdisco/address_set.rb
|
|
422
424
|
- lib/net/connector/netdisco/batch.rb
|
|
423
425
|
- lib/net/connector/netdisco/cli.rb
|
|
424
426
|
- lib/net/connector/netdisco/cli/backup.rb
|