net-ops 0.0.4.pre
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/lib/net/ops.rb +384 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f73921d344261f93ed38678f0be1ed30e058c786
|
4
|
+
data.tar.gz: 2dc6ca24c0ff3ef0fb849b96b9ed3f8967e1a9f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6571aa3b754121c2218abfde7d7f08586022ac6b84944aa290a8faae0deb642a5e21ec757ba621c05aee747b807d66120f943abf07b8edcf1c8bc6678aa89ff3
|
7
|
+
data.tar.gz: c3f8fbab57e7c2642363b9f4ae492c66b3ed12cf87a5343b8a237e9d0f78e1bb4b97bec4e0c8f2b2dbd9cb44fd1d468fc200dac32d73ae3b54a474c6714c5009
|
data/lib/net/ops.rb
ADDED
@@ -0,0 +1,384 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'thread/pool'
|
4
|
+
|
5
|
+
Dir['../lib/net/transport/*.rb'].each { |file| require file }
|
6
|
+
|
7
|
+
module Net
|
8
|
+
module Ops
|
9
|
+
|
10
|
+
# Provides a DSL for interacting with Cisco switches and routers.
|
11
|
+
class Session
|
12
|
+
|
13
|
+
attr_reader :transport
|
14
|
+
attr_reader :transports
|
15
|
+
|
16
|
+
# Initialize a new session.
|
17
|
+
#
|
18
|
+
# @param [String] host the target host.
|
19
|
+
#
|
20
|
+
# @param [Hash<Symbol, String>] options an Hash containing the transport options.
|
21
|
+
# @option options [String] :timeout The timeout before raising an exception while waiting for output.
|
22
|
+
# @option options [String, Regexp] :prompt A String or a Regexp that match the prompt of the device.
|
23
|
+
#
|
24
|
+
# @param [Logger] logger the logger to use.
|
25
|
+
#
|
26
|
+
# @return [Session] the new session.
|
27
|
+
def initialize(host, options = { timeout: 10, prompt: /.+(#|>)/ }, logger = nil)
|
28
|
+
@host = host
|
29
|
+
@options = options
|
30
|
+
@transports = []
|
31
|
+
|
32
|
+
setup_logger(logger)
|
33
|
+
|
34
|
+
Net::Ops::Transport.constants.each do |c|
|
35
|
+
register_transport(c) if Class === Net::Ops::Transport.const_get(c)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Open the session to the device.
|
40
|
+
#
|
41
|
+
# @param [Hash<Symbol, String>] credentials an Hash containing the credentials used to login.
|
42
|
+
# @option credentials [String] :username The username.
|
43
|
+
# @option credentials [String] :password The password.
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
# @raise [Net::Ops::TransportUnavailable] if the session cannot be opened.
|
47
|
+
def open(credentials)
|
48
|
+
@credentials = credentials
|
49
|
+
|
50
|
+
@logger.debug(@host) { "Opening session as #{ credentials[:username] }" }
|
51
|
+
|
52
|
+
@transports.each do |transport|
|
53
|
+
@transport ||= transport.open(@host, @options, credentials)
|
54
|
+
end
|
55
|
+
|
56
|
+
fail Net::Ops::TransportUnavailable unless @transport
|
57
|
+
end
|
58
|
+
|
59
|
+
# Close the session to the device.
|
60
|
+
#
|
61
|
+
# @return [void]
|
62
|
+
def close
|
63
|
+
@logger.debug(@host) { 'Closing session' }
|
64
|
+
@transport.close
|
65
|
+
@transport = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Measure the latency.
|
69
|
+
#
|
70
|
+
# @return [Integer] the time to run and retrieve the output of a command in milliseconds.
|
71
|
+
def latency
|
72
|
+
t1 = Time.now
|
73
|
+
@transport.cmd('')
|
74
|
+
t2 = Time.now
|
75
|
+
|
76
|
+
(t2 - t1) * 1000.0
|
77
|
+
end
|
78
|
+
|
79
|
+
# Send the specified command to the device and wait for the output.
|
80
|
+
#
|
81
|
+
# @param [String] command the command to run.
|
82
|
+
# @return [String] the output of the command.
|
83
|
+
def run(command)
|
84
|
+
@logger.debug("#{ @host } (#{ get_mode })") { "Executing #{ command }" }
|
85
|
+
|
86
|
+
output = ''
|
87
|
+
@transport.cmd(command) { |c| output += c }
|
88
|
+
|
89
|
+
@logger.debug("#{ @host } (#{ get_mode })") { output }
|
90
|
+
# @logger.warn(@host) { 'Net::Ops::IOSInvalidInput'; puts output } if /nvalid input detected/.match(output)
|
91
|
+
fail Net::Ops::IOSInvalidInput if /nvalid input detected/.match(output)
|
92
|
+
|
93
|
+
output
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get the specified item on the device.
|
97
|
+
# Equivalent to the Cisco show command.
|
98
|
+
#
|
99
|
+
# @param item [String] the item to get.
|
100
|
+
# @return [String] the item.
|
101
|
+
def get(item)
|
102
|
+
run("show #{ item }")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set the value for the specified item on the device.
|
106
|
+
#
|
107
|
+
# @param item [String] the item to configure.
|
108
|
+
# @param value [String] the value to assign to the item.
|
109
|
+
# @return [String] the eventual output of the command.
|
110
|
+
def set(item, value)
|
111
|
+
run("#{ item } #{ value }")
|
112
|
+
end
|
113
|
+
|
114
|
+
# Enable the specified item on the device.
|
115
|
+
#
|
116
|
+
# @param item [String] the item to enable.
|
117
|
+
# @return [String] the eventual output of the command.
|
118
|
+
def enable(item)
|
119
|
+
run(item)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Disable the specified item on the device.
|
123
|
+
# Equivalent to the Cisco no command.
|
124
|
+
#
|
125
|
+
# @param item [String] the item to enable.
|
126
|
+
# @return [String] the eventual output of the command.
|
127
|
+
def disable(item)
|
128
|
+
run("no #{ item }")
|
129
|
+
end
|
130
|
+
|
131
|
+
def zeroize(item)
|
132
|
+
@logger.debug(@host) { "Executing #{ item } zeroize" }
|
133
|
+
|
134
|
+
@transport.cmd('String' => "#{ item } zeroize", 'Match' => /.+/)
|
135
|
+
@transport.cmd('yes')
|
136
|
+
end
|
137
|
+
|
138
|
+
def generate(item, options)
|
139
|
+
run("#{ item } generate #{ options }")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Run the specified command in the privileged mode on the device.
|
143
|
+
#
|
144
|
+
# @param [String] command the command to run.
|
145
|
+
# @return [String] the output of the command.
|
146
|
+
def exec(command)
|
147
|
+
ensure_mode(:privileged)
|
148
|
+
run(command)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Run the specified command in the configuration mode on the device.
|
152
|
+
#
|
153
|
+
# @param [String] command the command to run.
|
154
|
+
# @return [String] the output of the command.
|
155
|
+
def config(command)
|
156
|
+
ensure_mode(:configuration)
|
157
|
+
run(command)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Save the configuration of the device.
|
161
|
+
# Equivalent to the Cisco copy running-config startup-config command.
|
162
|
+
# @return [void]
|
163
|
+
def write!
|
164
|
+
ensure_mode(:privileged)
|
165
|
+
exec('write memory')
|
166
|
+
end
|
167
|
+
|
168
|
+
# Run the specified block in the privileged mode on the device.
|
169
|
+
#
|
170
|
+
# @param [Block] block the block to run.
|
171
|
+
# @return [void]
|
172
|
+
def privileged(&block)
|
173
|
+
ensure_mode(:privileged)
|
174
|
+
instance_eval(&block)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Run the specified block in the configuration mode on the device.
|
178
|
+
#
|
179
|
+
# @param [Block] block the block to run.
|
180
|
+
# @return [void]
|
181
|
+
def configuration(options = nil, &block)
|
182
|
+
ensure_mode(:configuration)
|
183
|
+
instance_eval(&block)
|
184
|
+
|
185
|
+
write! if options == :enforce_save
|
186
|
+
end
|
187
|
+
|
188
|
+
def interface(interface, &block)
|
189
|
+
ensure_mode(:configuration)
|
190
|
+
|
191
|
+
run("interface #{ interface }")
|
192
|
+
instance_eval(&block)
|
193
|
+
end
|
194
|
+
|
195
|
+
def interfaces(interfaces = /.+/, &block)
|
196
|
+
ints = privileged do
|
197
|
+
get('interfaces status').select do |int|
|
198
|
+
interfaces.match("#{ int['short_type'] }#{ int['port_number'] }")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
ints.each do |int|
|
203
|
+
interface("#{ int['short_type'] }#{ int['port_number'] }") do
|
204
|
+
instance_eval(&block)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def lines(lines, &block)
|
210
|
+
ensure_mode(:configuration)
|
211
|
+
|
212
|
+
run("line #{ lines }")
|
213
|
+
instance_eval(&block)
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
def register_transport(klass)
|
219
|
+
@logger.debug(@host) { "Registering transport #{ klass }" }
|
220
|
+
@transports << Net::Ops::Transport.const_get(klass)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Create a default logger if none is specified.
|
224
|
+
#
|
225
|
+
# @param [Logger] logger the logger to use.
|
226
|
+
# @return [void]
|
227
|
+
def setup_logger(logger = nil)
|
228
|
+
# If a logger is specified we replace the existing.
|
229
|
+
@logger = logger
|
230
|
+
|
231
|
+
# Otherwise we create a new one.
|
232
|
+
# logger = Logger.new(STDOUT)
|
233
|
+
logger = Logger.new('log.log')
|
234
|
+
logger.level = Logger::DEBUG
|
235
|
+
@logger ||= logger
|
236
|
+
end
|
237
|
+
|
238
|
+
# Get the current command mode.
|
239
|
+
#
|
240
|
+
# @return [Symbol] the current command mode.
|
241
|
+
def get_mode
|
242
|
+
prompt = ''
|
243
|
+
@transport.cmd('') { |c| prompt += c }
|
244
|
+
match = /(?<hostname>[^\(-\)]+)(\((?<text>[\w\-]+)\))?(?<char>#|>)/.match(prompt)
|
245
|
+
|
246
|
+
mode = nil
|
247
|
+
|
248
|
+
if match && match['char']
|
249
|
+
|
250
|
+
mode = case match['char']
|
251
|
+
when '>' then :user
|
252
|
+
when '#' then :privileged
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
if match && match['text']
|
258
|
+
mode = match['text'].to_sym
|
259
|
+
end
|
260
|
+
|
261
|
+
mode
|
262
|
+
end
|
263
|
+
|
264
|
+
# Ensure the CLI is currently in the specified command mode.
|
265
|
+
#
|
266
|
+
# @param [Symbol] mode the target command mode.
|
267
|
+
# @return [void]
|
268
|
+
def ensure_mode(mode)
|
269
|
+
case mode
|
270
|
+
|
271
|
+
when :user
|
272
|
+
run('end') if configuration?
|
273
|
+
|
274
|
+
when :privileged
|
275
|
+
run('end') if configuration?
|
276
|
+
enable_privileged(@credentials['password']) if user?
|
277
|
+
|
278
|
+
when :configuration
|
279
|
+
run('configure terminal') unless configuration?
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Check if the CLI is in user mode.
|
285
|
+
#
|
286
|
+
# @return [Boolean]
|
287
|
+
def user?
|
288
|
+
get_mode == :user
|
289
|
+
end
|
290
|
+
|
291
|
+
# Check if the CLI is in privileged mode.
|
292
|
+
#
|
293
|
+
# @return [Boolean]
|
294
|
+
def privileged?
|
295
|
+
get_mode == :privileged
|
296
|
+
end
|
297
|
+
|
298
|
+
# Check if the CLI is in configuration mode.
|
299
|
+
#
|
300
|
+
# @return [Boolean]
|
301
|
+
def configuration?
|
302
|
+
get_mode.to_s.include?('config')
|
303
|
+
end
|
304
|
+
|
305
|
+
# Go from user mode to privileged mode.
|
306
|
+
#
|
307
|
+
# @param [String] the enable password.
|
308
|
+
# @return [void]
|
309
|
+
def enable_privileged(password)
|
310
|
+
@transport.cmd('String' => 'enable', 'Match' => /.+assword.+/)
|
311
|
+
@transport.cmd(password)
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
class Parser
|
317
|
+
|
318
|
+
def initialize(file)
|
319
|
+
@regexs = YAML.load_file(file)
|
320
|
+
end
|
321
|
+
|
322
|
+
def parse(command, output)
|
323
|
+
results = []
|
324
|
+
path = explore_tree(command.split(/ /))
|
325
|
+
|
326
|
+
if path.has_key?('regex')
|
327
|
+
regex = Regexp.new(path.fetch('regex').delete(' '))
|
328
|
+
|
329
|
+
output.each_line do |line|
|
330
|
+
results << regex.match(line) if regex.match(line)
|
331
|
+
end
|
332
|
+
|
333
|
+
else results = output
|
334
|
+
end
|
335
|
+
|
336
|
+
results
|
337
|
+
end
|
338
|
+
|
339
|
+
private
|
340
|
+
|
341
|
+
def explore_tree(path)
|
342
|
+
level = @regexs['cisco']
|
343
|
+
|
344
|
+
path.each { |p| level[p] ? level = level[p] : break }
|
345
|
+
|
346
|
+
level
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
class Task
|
352
|
+
include Net::Ops
|
353
|
+
|
354
|
+
def initialize(id)
|
355
|
+
@id = id
|
356
|
+
|
357
|
+
@logger = Logger.new(STDOUT)
|
358
|
+
@logger.level = Logger::INFO
|
359
|
+
end
|
360
|
+
|
361
|
+
def log(severity, message)
|
362
|
+
@logger.add(severity, message, @id)
|
363
|
+
end
|
364
|
+
|
365
|
+
def info(message)
|
366
|
+
log(Logger::INFO, message)
|
367
|
+
end
|
368
|
+
|
369
|
+
def warn(message)
|
370
|
+
log(Logger::WARN, message)
|
371
|
+
end
|
372
|
+
|
373
|
+
def error(message)
|
374
|
+
log(Logger::ERROR, message)
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
#
|
380
|
+
class TransportUnavailable < Exception; end
|
381
|
+
class IOSInvalidInput < Exception; end
|
382
|
+
|
383
|
+
end
|
384
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: net-ops
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Maxime Mouchet
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thread
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh-telnet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.0.2
|
41
|
+
description: Framework for interacting with network devices.
|
42
|
+
email:
|
43
|
+
- max@maxmouchet.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/net/ops.rb
|
49
|
+
homepage: http://github.com/maxmouchet/qscripts
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - '>'
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.3.1
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.0.3
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: Net::Ops
|
73
|
+
test_files: []
|
74
|
+
has_rdoc:
|