net-ops 0.0.5.pre → 0.0.6.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.
@@ -0,0 +1,308 @@
1
+ module Net; module Ops
2
+
3
+ # Provides a DSL for interacting with Cisco switches and routers.
4
+ class Session
5
+
6
+ attr_reader :transport
7
+ attr_reader :transports
8
+
9
+ # Initialize a new session.
10
+ #
11
+ # @param [String] host the target host.
12
+ #
13
+ # @param [Hash<Symbol, String>] options an Hash containing the transport options.
14
+ # @option options [String] :timeout The timeout before raising an exception while waiting for output.
15
+ # @option options [String, Regexp] :prompt A String or a Regexp that match the prompt of the device.
16
+ #
17
+ # @param [Logger] logger the logger to use.
18
+ #
19
+ # @return [Session] the new session.
20
+ def initialize(host, options = { timeout: 10, prompt: /.+(#|>|\])/ }, logger = nil)
21
+ @host = host
22
+ @options = options
23
+ @transports = []
24
+
25
+ setup_logger(logger)
26
+
27
+ Net::Ops::Transport.constants.each do |c|
28
+ register_transport(c) if Class === Net::Ops::Transport.const_get(c)
29
+ end
30
+ end
31
+
32
+ # Open the session to the device.
33
+ #
34
+ # @param [Hash<Symbol, String>] credentials an Hash containing the credentials used to login.
35
+ # @option credentials [String] :username The username.
36
+ # @option credentials [String] :password The password.
37
+ #
38
+ # @return [void]
39
+ # @raise [Net::Ops::TransportUnavailable] if the session cannot be opened.
40
+ def open(credentials)
41
+ @credentials = credentials
42
+
43
+ @logger.debug(@host) { "Opening session as #{ credentials[:username] }" }
44
+
45
+ @transports.each do |transport|
46
+ @transport ||= transport.open(@host, @options, credentials)
47
+ end
48
+
49
+ fail Net::Ops::TransportUnavailable unless @transport
50
+ end
51
+
52
+ # Close the session to the device.
53
+ #
54
+ # @return [void]
55
+ def close
56
+ @logger.debug(@host) { 'Closing session' }
57
+ @transport.close
58
+ @transport = nil
59
+ end
60
+
61
+ # Measure the latency.
62
+ #
63
+ # @return [Integer] the time to run and retrieve the output of a command in milliseconds.
64
+ def latency
65
+ t1 = Time.now
66
+ @transport.cmd('')
67
+ t2 = Time.now
68
+
69
+ (t2 - t1) * 1000.0
70
+ end
71
+
72
+ # Send the specified command to the device and wait for the output.
73
+ #
74
+ # @param [String] command the command to run.
75
+ # @return [String] the output of the command.
76
+ def run(command)
77
+ @logger.debug("#{ @host } (#{ get_mode })") { "Executing #{ command }" }
78
+
79
+ output = ''
80
+ @transport.cmd(command) { |c| output += c }
81
+
82
+ # @logger.debug("#{ @host } (#{ get_mode })") { output }
83
+ # @logger.warn(@host) { 'Net::Ops::IOSInvalidInput'; puts output } if /nvalid input detected/.match(output)
84
+ fail Net::Ops::IOSInvalidInput if /nvalid input detected/.match(output)
85
+
86
+ output
87
+ end
88
+
89
+ # Get the specified item on the device.
90
+ # Equivalent to the Cisco show command.
91
+ #
92
+ # @param item [String] the item to get.
93
+ # @return [String] the item.
94
+ def get(item)
95
+ run("show #{ item }")
96
+ end
97
+
98
+ # Set the value for the specified item on the device.
99
+ #
100
+ # @param item [String] the item to configure.
101
+ # @param value [String] the value to assign to the item.
102
+ # @return [String] the eventual output of the command.
103
+ def set(item, value)
104
+ run("#{ item } #{ value }")
105
+ end
106
+
107
+ # Enable the specified item on the device.
108
+ #
109
+ # @param item [String] the item to enable.
110
+ # @return [String] the eventual output of the command.
111
+ def enable(item)
112
+ run(item)
113
+ end
114
+
115
+ # Disable the specified item on the device.
116
+ # Equivalent to the Cisco no command.
117
+ #
118
+ # @param item [String] the item to enable.
119
+ # @return [String] the eventual output of the command.
120
+ def disable(item)
121
+ run("no #{ item }")
122
+ end
123
+
124
+ def zeroize(item)
125
+ @logger.debug(@host) { "Executing #{ item } zeroize" }
126
+
127
+ @transport.cmd('String' => "#{ item } zeroize", 'Match' => /.+/)
128
+ @transport.cmd('yes')
129
+ end
130
+
131
+ def generate(item, options)
132
+ run("#{ item } generate #{ options }")
133
+ end
134
+
135
+ # Run the specified command in the privileged mode on the device.
136
+ #
137
+ # @param [String] command the command to run.
138
+ # @return [String] the output of the command.
139
+ def exec(command)
140
+ ensure_mode(:privileged)
141
+ run(command)
142
+ end
143
+
144
+ # Run the specified command in the configuration mode on the device.
145
+ #
146
+ # @param [String] command the command to run.
147
+ # @return [String] the output of the command.
148
+ def config(command)
149
+ ensure_mode(:configuration)
150
+ run(command)
151
+ end
152
+
153
+ # Save the configuration of the device.
154
+ # Equivalent to the Cisco copy running-config startup-config command.
155
+ # @return [void]
156
+ def write!
157
+ ensure_mode(:privileged)
158
+ exec('write memory')
159
+ end
160
+
161
+ # Run the specified block in the privileged mode on the device.
162
+ #
163
+ # @param [Block] block the block to run.
164
+ # @return [void]
165
+ def privileged(&block)
166
+ ensure_mode(:privileged)
167
+ instance_eval(&block)
168
+ end
169
+
170
+ # Run the specified block in the configuration mode on the device.
171
+ #
172
+ # @param [Block] block the block to run.
173
+ # @return [void]
174
+ def configuration(options = nil, &block)
175
+ ensure_mode(:configuration)
176
+ instance_eval(&block)
177
+
178
+ write! if options == :enforce_save
179
+ end
180
+
181
+ def interface(interface, &block)
182
+ ensure_mode(:configuration)
183
+
184
+ run("interface #{ interface }")
185
+ instance_eval(&block)
186
+ end
187
+
188
+ def interfaces(interfaces = /.+/, &block)
189
+ ints = privileged do
190
+ get('interfaces status').select do |int|
191
+ interfaces.match("#{ int['short_type'] }#{ int['port_number'] }")
192
+ end
193
+ end
194
+
195
+ ints.each do |int|
196
+ interface("#{ int['short_type'] }#{ int['port_number'] }") do
197
+ instance_eval(&block)
198
+ end
199
+ end
200
+ end
201
+
202
+ def lines(lines, &block)
203
+ ensure_mode(:configuration)
204
+
205
+ run("line #{ lines }")
206
+ instance_eval(&block)
207
+ end
208
+
209
+ private
210
+
211
+ def register_transport(klass)
212
+ @logger.debug(@host) { "Registering transport #{ klass }" }
213
+ @transports << Net::Ops::Transport.const_get(klass)
214
+ end
215
+
216
+ # Create a default logger if none is specified.
217
+ #
218
+ # @param [Logger] logger the logger to use.
219
+ # @return [void]
220
+ def setup_logger(logger = nil)
221
+ # If a logger is specified we replace the existing.
222
+ @logger = logger
223
+
224
+ # Otherwise we create a new one.
225
+ logger = Logger.new(STDOUT)
226
+ logger.level = Logger::DEBUG
227
+ @logger ||= logger
228
+ end
229
+
230
+ # Get the current command mode.
231
+ #
232
+ # @return [Symbol] the current command mode.
233
+ def get_mode
234
+ prompt = ''
235
+ @transport.cmd('') { |c| prompt += c }
236
+ match = /(?<hostname>[^\(-\)]+)(\((?<text>[\w\-]+)\))?(?<char>#|>)/.match(prompt)
237
+
238
+ mode = nil
239
+
240
+ if match && match['char']
241
+
242
+ mode = case match['char']
243
+ when '>' then :user
244
+ when '#' then :privileged
245
+ end
246
+
247
+ end
248
+
249
+ if match && match['text']
250
+ mode = match['text'].to_sym
251
+ end
252
+
253
+ mode
254
+ end
255
+
256
+ # Ensure the CLI is currently in the specified command mode.
257
+ #
258
+ # @param [Symbol] mode the target command mode.
259
+ # @return [void]
260
+ def ensure_mode(mode)
261
+ case mode
262
+
263
+ when :user
264
+ run('end') if configuration?
265
+
266
+ when :privileged
267
+ run('end') if configuration?
268
+ enable_privileged(@credentials[:password]) if user?
269
+
270
+ when :configuration
271
+ run('configure terminal') unless configuration?
272
+
273
+ end
274
+ end
275
+
276
+ # Check if the CLI is in user mode.
277
+ #
278
+ # @return [Boolean]
279
+ def user?
280
+ get_mode == :user
281
+ end
282
+
283
+ # Check if the CLI is in privileged mode.
284
+ #
285
+ # @return [Boolean]
286
+ def privileged?
287
+ get_mode == :privileged
288
+ end
289
+
290
+ # Check if the CLI is in configuration mode.
291
+ #
292
+ # @return [Boolean]
293
+ def configuration?
294
+ get_mode.to_s.include?('config')
295
+ end
296
+
297
+ # Go from user mode to privileged mode.
298
+ #
299
+ # @param [String] the enable password.
300
+ # @return [void]
301
+ def enable_privileged(password)
302
+ @transport.cmd('String' => 'enable', 'Match' => /.+assword.+/)
303
+ @transport.cmd(password)
304
+ end
305
+
306
+ end
307
+
308
+ end; end
@@ -0,0 +1,32 @@
1
+ module Net; module Ops
2
+
3
+ #
4
+ class Task
5
+ include Net::Ops
6
+
7
+ def initialize(id)
8
+ @id = id
9
+
10
+ @logger = Logger.new(STDOUT)
11
+ @logger.level = Logger::INFO
12
+ end
13
+
14
+ def log(severity, message)
15
+ @logger.add(severity, message, @id)
16
+ end
17
+
18
+ def info(message)
19
+ log(Logger::INFO, message)
20
+ end
21
+
22
+ def warn(message)
23
+ log(Logger::WARN, message)
24
+ end
25
+
26
+ def error(message)
27
+ log(Logger::ERROR, message)
28
+ end
29
+
30
+ end
31
+
32
+ end; end
@@ -0,0 +1,35 @@
1
+ require 'net/ssh/telnet'
2
+
3
+ module Net; module Ops; module Transport
4
+
5
+ #
6
+ class SSH
7
+
8
+ # Open an SSH session to the specified host using net/ssh/telnet.
9
+ #
10
+ # @param host [String] the destination host.
11
+ # @param options [Hash]
12
+ # @param credentials [Hash] credentials to use to connect.
13
+ def self.open(host, options, credentials)
14
+ session = nil
15
+
16
+ ssh = Net::SSH.start(host, credentials[:username], :password => credentials[:password])
17
+ session = Net::SSH::Telnet.new('Session' => ssh,
18
+ 'Timeout' => options[:timeout],
19
+ 'Prompt' => options[:prompt])
20
+
21
+ rescue Errno::ECONNREFUSED => e
22
+ session = nil
23
+
24
+ rescue Net::SSH::AuthenticationFailed => e
25
+ session = nil
26
+
27
+ rescue Exception => e
28
+ session = nil
29
+
30
+ return session
31
+ end
32
+
33
+ end
34
+
35
+ end; end; end
@@ -0,0 +1,49 @@
1
+ require 'net/telnet'
2
+
3
+ module Net; module Ops; module Transport
4
+
5
+ #
6
+ class Telnet
7
+
8
+ # Open a Telnet session to the specified host using net/ssh.
9
+ #
10
+ # @param host [String] the destination host.
11
+ # @param options [Hash]
12
+ # @param credentials [Hash] credentials to use to connect.
13
+ def self.open(host, options, credentials)
14
+ session = nil
15
+
16
+ session = Net::Telsnet.new('Host' => host,
17
+ 'Timeout' => options[:timeout],
18
+ 'Prompt' => options[:prompt])
19
+
20
+ output = ''
21
+ session.cmd('String' => '', 'Match' => /.+/) { |c| output += c }
22
+
23
+ if /[Uu]sername:/.match(output) then
24
+ session.cmd('String' => credentials[:username],
25
+ 'Match' => /.+/)
26
+ session.cmd(credentials[:password])
27
+ end
28
+
29
+ if /[Pp]assword:/.match(output) then
30
+ session.cmd(credentials[:password])
31
+ end
32
+
33
+ return session
34
+
35
+ rescue Errno::ECONNREFUSED => e
36
+ session = nil
37
+
38
+ rescue Net::OpenTimeout => e
39
+ session = nil
40
+
41
+ rescue Exception => e
42
+ session = nil
43
+
44
+ return session
45
+ end
46
+
47
+ end
48
+
49
+ end; end; end
@@ -0,0 +1,5 @@
1
+ module Net
2
+ module Ops
3
+ VERSION = '0.0.6.pre'
4
+ end
5
+ end
data/net-ops.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'net/ops/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'net-ops'
8
+ spec.version = Net::Ops::VERSION
9
+ spec.authors = ['Maxime Mouchet']
10
+ spec.email = ['mouchet.max@gmail.com']
11
+ spec.description = %q{Framework to automate daily operations on network devices.}
12
+ spec.summary = %q{Net::Ops}
13
+ spec.homepage = 'http://github.com/maxmouchet/qscripts'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'thread', '~> 0.1'
22
+ spec.add_runtime_dependency 'net-ssh-telnet', '~> 0.0.2'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake'
26
+ end