net-ops 0.0.5.pre → 0.0.6.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +334 -0
- data/Rakefile +1 -0
- data/examples/basic_script.rb +94 -0
- data/examples/commands.txt +6 -0
- data/examples/exec_file.rb +21 -0
- data/examples/hosts.txt +2 -0
- data/lib/net/ops.rb +10 -373
- data/lib/net/ops/parser.rb +39 -0
- data/lib/net/ops/session.rb +308 -0
- data/lib/net/ops/task.rb +32 -0
- data/lib/net/ops/transport/ssh.rb +35 -0
- data/lib/net/ops/transport/telnet.rb +49 -0
- data/lib/net/ops/version.rb +5 -0
- data/net-ops.gemspec +26 -0
- data/regexs.yml +12 -0
- data/tasks/downcase_hostname_task.rb +144 -0
- data/tasks/test.rb +21 -0
- metadata +50 -5
- data/lib/net/transport/ssh.rb +0 -39
- data/lib/net/transport/telnet.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34f9d13e55ceb5c908ac02977a65af34cf800351
|
4
|
+
data.tar.gz: 590c49b48e2f3332638b6fbc15957db6986345c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0a759af3c60da04e28c79483b2f373b53747b535c12165202a65fa08291cef52a6020f8f492fdbd57ee0ae52557ac92e4eb8df348867a9fbdd6da3532c8b687
|
7
|
+
data.tar.gz: 3aed13236fa58b023460b17c326cf4314c8d9ddc868a39349b38137231734de471821c4b02d756fb21b2ed9c0841c95d149cec01ff119d90573e0673272b8f17
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
coverage
|
6
|
+
InstalledFiles
|
7
|
+
lib/bundler/man
|
8
|
+
pkg
|
9
|
+
rdoc
|
10
|
+
spec/reports
|
11
|
+
test/tmp
|
12
|
+
test/version_tmp
|
13
|
+
tmp
|
14
|
+
Gemfile.lock
|
15
|
+
|
16
|
+
# YARD artifacts
|
17
|
+
.yardoc
|
18
|
+
_yardoc
|
19
|
+
doc/
|
20
|
+
|
21
|
+
credentials.yml
|
22
|
+
*~
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Maxime Mouchet
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
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, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
# Net::Ops
|
2
|
+
|
3
|
+
## Ruby framework for interacting with network devices
|
4
|
+
|
5
|
+
Computers are made to simplify our lives, not make them more complicated. They don't mind doing 1000x the same thing but too often people do repetitive tasks at hand because they don't know how to write scripts.
|
6
|
+
I developed this little Ruby module to simplify daily operations on network devices like switches, routers, and access-points.
|
7
|
+
|
8
|
+
### Prerequisites
|
9
|
+
|
10
|
+
I made it to be as simple as possible but, if you want to understand how it works or extend it, you will need some Ruby knowledge.
|
11
|
+
[The Little Book of Ruby](http://www.sapphiresteel.com/The-Little-Book-Of-Ruby) is a good introduction altough convention are not always clear.
|
12
|
+
[Design Patterns](http://www.amazon.fr/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) and [Design Patterns in Ruby](http://www.pearsonhighered.com/educator/product/Design-Patterns-in-Ruby/9780321490452.page)
|
13
|
+
are must read.
|
14
|
+
[Stack Overflow](http://stackoverflow.com/) is a good place in case of problem.
|
15
|
+
|
16
|
+
### Compatibility
|
17
|
+
|
18
|
+
Tested with Cisco IOS and IOS XE devices. Should work partially with NX-OS.
|
19
|
+
Not compatible with IOS XR and non-Cisco devices but it would be possible to add an abstraction layer in ops.rb to support other brands.
|
20
|
+
|
21
|
+
I implemented only two [transports](#transports), Telnet and SSH. If you want to use another protocol (a serial link for example) you will have to implement it.
|
22
|
+
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
First, you need a Ruby interpreter. [MRI](http://en.wikipedia.org/wiki/Ruby_MRI), the reference implementation, is a good choice. You can download it on [ruby-lang.org](http://www.ruby-lang.org/en/downloads/).
|
27
|
+
Note that MRI is already included in Mac OS X and most of the Linux distributions.
|
28
|
+
On Ubuntu you can install it with `apt-get install ruby1.9.3`.
|
29
|
+
There is other Ruby implementation like [JRuby](http://jruby.org/) or [MagLev](http://maglev.github.io/) but I have not tested my code with them.
|
30
|
+
|
31
|
+
Then you should intall net-ops. You can get the latest version from RubyGems:
|
32
|
+
```bash
|
33
|
+
gem install 'net-ops'
|
34
|
+
```
|
35
|
+
|
36
|
+
Or build it from the source:
|
37
|
+
```bash
|
38
|
+
gem build net-ops.gemspec
|
39
|
+
gem install ./net-ops-x.y.z.gem
|
40
|
+
```
|
41
|
+
|
42
|
+
## Getting started
|
43
|
+
|
44
|
+
You need to require net/ops in all your scripts and [tasks](#tasks) (we will talk about this later):
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'net/ops'
|
48
|
+
```
|
49
|
+
|
50
|
+
To run a script you can double-click on it (on Windows) or issue `ruby my_script.rb` in a terminal.
|
51
|
+
|
52
|
+
### Storing credentials
|
53
|
+
|
54
|
+
Writing directly your username and password directly in the script is a bad idea.
|
55
|
+
If you want to keep things simple you can store them in a [YAML](http://en.wikipedia.org/wiki/YAML) file with the following structure:
|
56
|
+
```yml
|
57
|
+
# credentials.yml
|
58
|
+
username: user1
|
59
|
+
password: r5Xqx8
|
60
|
+
```
|
61
|
+
Then, to use them in your script:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
credentials = YAML.load_file('credentials.yml')
|
65
|
+
|
66
|
+
credentials.fetch('username') #=> 'user1'
|
67
|
+
credentials.fetch('password') #=> 'r5Xqx8'
|
68
|
+
```
|
69
|
+
|
70
|
+
*Note 1 : If you don't need a username to connect to your device you can either let it empty or specify any value. The field will be ignored.*
|
71
|
+
*Note 2 : Currently the login and the enable password used are the same ([issue #4](https://github.com/maxmouchet/net-ops/issues/4))*
|
72
|
+
|
73
|
+
### Connecting to a device
|
74
|
+
|
75
|
+
Connecting to a device is a two-step process: create a session, and open it.
|
76
|
+
Nothing is sent on the transport until you open the session.
|
77
|
+
|
78
|
+
#### Create the session
|
79
|
+
|
80
|
+
To create a Session you just need to specify the hostname (or the IP address):
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
@session = Net::Ops::Session.new('router1.local')
|
84
|
+
```
|
85
|
+
|
86
|
+
##### Options
|
87
|
+
|
88
|
+
You can also customize the timeout (`Integer`) and the prompt (`Regexp`) if you want:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
host = 'router1.local'
|
92
|
+
options = { timeout: 10, prompt: /.+(#|>)/ }
|
93
|
+
|
94
|
+
@session = Net::Ops::Session.new(host, options)
|
95
|
+
```
|
96
|
+
|
97
|
+
##### Logging
|
98
|
+
|
99
|
+
By default `Session` logs everything from `Level::DEBUG` to `STDOUT`. You can specify a custom logger to the constructor.
|
100
|
+
For example to log everything from `Level::WARN` to a file:
|
101
|
+
```ruby
|
102
|
+
logger = Logger.new('logfile.log')
|
103
|
+
logger.level = Logger::WARN
|
104
|
+
|
105
|
+
@session = Net::Ops::Session.new(host, options, logger)
|
106
|
+
```
|
107
|
+
|
108
|
+
#### Open the session
|
109
|
+
|
110
|
+
Given you loaded your credentials from a YAML file you can open the session like this:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
@session.open({ username: credentials.fetch('username'),
|
114
|
+
password: credentials.fetch('password') })
|
115
|
+
```
|
116
|
+
|
117
|
+
Note that this doesn't handle `Net::Ops::TransportUnavailable` which is raised when no transport can be used to open the session.
|
118
|
+
To show the error and prevent your script from stopping:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
begin @session.open({ username: '', password: '' })
|
122
|
+
rescue Net::Ops::TransportUnavailable => e
|
123
|
+
puts "There is an error: #{e.message}"
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Close the session
|
128
|
+
|
129
|
+
It is generally not needed to close the session since the Ruby garbage collector will do it automatically.
|
130
|
+
However if you need to, you can call `close`:
|
131
|
+
```ruby
|
132
|
+
@session.close
|
133
|
+
```
|
134
|
+
|
135
|
+
### Sending commands
|
136
|
+
|
137
|
+
Once the session is opened you can send commands to the device. Net::Ops offer three abstraction levels that are described below.
|
138
|
+
|
139
|
+
#### Raw commands
|
140
|
+
|
141
|
+
The basic way to send a command and get the output is the `run(command)` method.
|
142
|
+
It send command (`String`) followed by a carriage return to the device, wait for the prompt, and return what happened between.
|
143
|
+
For example, to get `show int status` output:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
puts @session.run('show int status')
|
147
|
+
```
|
148
|
+
|
149
|
+
`run(command)` is pretty low-level but sometimes you will want to play directly with the transport.
|
150
|
+
For example when the command ask for confirmation and doesn't return the prompt (like `reload`). In this case you can do something like this:
|
151
|
+
```ruby
|
152
|
+
transport = @session.transport
|
153
|
+
transport.cmd('String' => 'reload', 'Match' => /.+confirm.+/)
|
154
|
+
transport.cmd('yes')
|
155
|
+
```
|
156
|
+
|
157
|
+
To get the output with `transport.cmd` you need to pass a block:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
transport = @session.transport
|
161
|
+
transport.cmd('show version') { |c| puts c }
|
162
|
+
```
|
163
|
+
|
164
|
+
|
165
|
+
#### Basic commands
|
166
|
+
|
167
|
+
To make your script easier to read, Net::Ops provides methods which are basically alias to Cisco commands.
|
168
|
+
These are `get(item)`, `set(item, value)`, `enable(item)`, and `disable(item)`:
|
169
|
+
```ruby
|
170
|
+
@session.get 'interfaces status'
|
171
|
+
# send 'show interfaces status'
|
172
|
+
|
173
|
+
@session.set 'terminal length', 0
|
174
|
+
# send 'terminal length 0'
|
175
|
+
|
176
|
+
@session.enable 'ip http secure-server'
|
177
|
+
# send 'ip http secure-server'
|
178
|
+
|
179
|
+
@session.disable 'spanning-tree bpduguard'
|
180
|
+
# send 'no spanning-tree bpduguard'
|
181
|
+
```
|
182
|
+
|
183
|
+
These methods allow you to write script that are easily readable but you can do much more by combining them with the [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) that Net::Ops provides.
|
184
|
+
|
185
|
+
#### Domain-specific language
|
186
|
+
|
187
|
+
Currently the DSL is made of five methods:
|
188
|
+
* `privileged(&block)`
|
189
|
+
* `configuration(options = nil, &block)`
|
190
|
+
* `interface(interface, &block)`
|
191
|
+
* `interfaces(interfaces, &block)`
|
192
|
+
* `lines(lines, &block)`
|
193
|
+
|
194
|
+
They allow you to run commands in the specified context. Note that don't have to prefix methods with `@session` since the block is evalued inside the session.
|
195
|
+
|
196
|
+
Here's an example of how to use it:
|
197
|
+
```ruby
|
198
|
+
# Here we pass a block to be executed in the privileged mode.
|
199
|
+
@session.privileged do
|
200
|
+
|
201
|
+
# Let's get interfaces status.
|
202
|
+
sw_interfaces = get 'interfaces status'
|
203
|
+
|
204
|
+
# Show disabled interfaces
|
205
|
+
nc_interfaces = sw_interfaces.select { |int| int['status'] == 'disabled' }
|
206
|
+
puts nc_interfaces
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# Do some stuff in configuration mode.
|
211
|
+
@session.configuration do
|
212
|
+
|
213
|
+
# Add description to Gi1/0/2.
|
214
|
+
# Note the singular/plural in interface(s).
|
215
|
+
# interface accept only String as an argument.
|
216
|
+
# interfaces accept Array, Regexp, and String.
|
217
|
+
interface('Gi1/0/2') do
|
218
|
+
set 'description', 'I am Gi1/0/2'
|
219
|
+
end
|
220
|
+
|
221
|
+
# Disable bpduguard on all Gig interfaces.
|
222
|
+
interfaces(/Gi1\/0/) do
|
223
|
+
disable 'spanning-tree bpduguard'
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
# Copy to startup-config
|
229
|
+
@session.write!
|
230
|
+
|
231
|
+
# Do something else in configuration mode
|
232
|
+
# but automatically write this time.
|
233
|
+
@session.configuration(:enforce_save) do
|
234
|
+
disable 'ip http secure-server'
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
#### Other commands
|
239
|
+
|
240
|
+
* `write!`
|
241
|
+
* `zeroize(item)`
|
242
|
+
* `generate(item, options)`
|
243
|
+
|
244
|
+
Example :
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
# Delete the crypto key
|
248
|
+
@session.zeroize 'crypto key'
|
249
|
+
|
250
|
+
# Regenerate it
|
251
|
+
@session.generate 'crypto key', 'rsa general-keys modulus 2048'
|
252
|
+
|
253
|
+
# Save running-config
|
254
|
+
@session.write!
|
255
|
+
```
|
256
|
+
|
257
|
+
## Tasks
|
258
|
+
Net::Ops allow to define tasks that perform a specific action and run it on several devices in parallel while handling errors and providing easy logging.
|
259
|
+
|
260
|
+
### Definition
|
261
|
+
To define a task you should create a new class that inherit from `Task` and define `initialize` and `work` methods:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
# my_task.rb
|
265
|
+
class MyTask < Net::Ops::Task
|
266
|
+
|
267
|
+
def initialize(id)
|
268
|
+
# Setup your stuff.
|
269
|
+
super(id)
|
270
|
+
end
|
271
|
+
|
272
|
+
def work
|
273
|
+
# Place your logic here.
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
`id` is an identifier that should be unique for each instance of your task. You can use whatever you want, for example the hostname of the device you are currently working on.
|
280
|
+
|
281
|
+
### Execution
|
282
|
+
To run a task you can basically instance it and call work:
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
t = MyTask.new('task1')
|
286
|
+
t.work
|
287
|
+
```
|
288
|
+
|
289
|
+
However you may want to run several tasks in parallel to speed up things. You can do that thanks to [thread/pool](https://github.com/meh/ruby-thread):
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
hosts = %w( host1 host2 host3 )
|
293
|
+
max_conn = 2
|
294
|
+
|
295
|
+
pool = Thread.pool(max_conn)
|
296
|
+
|
297
|
+
hosts.each do |hosts|
|
298
|
+
pool.process { MyTask.new(host).work }
|
299
|
+
end
|
300
|
+
|
301
|
+
pool.shutdown
|
302
|
+
```
|
303
|
+
|
304
|
+
## Transports
|
305
|
+
|
306
|
+
### Built-in
|
307
|
+
|
308
|
+
### Custom
|
309
|
+
```ruby
|
310
|
+
class MyCustomTransport
|
311
|
+
|
312
|
+
def self.open(host, options, credentials)
|
313
|
+
session = # Do what you need to get a session to the host.
|
314
|
+
return session
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
```
|
319
|
+
|
320
|
+
## Documentation
|
321
|
+
You can get the documentation via gem with `gem server`.
|
322
|
+
Or generate it manually with `rake doc`.
|
323
|
+
|
324
|
+
## Todo - Ideas
|
325
|
+
|
326
|
+
See [issues](https://github.com/maxmouchet/net-ops/issues?state=open).
|
327
|
+
|
328
|
+
## Contributing
|
329
|
+
|
330
|
+
1. Fork it
|
331
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
332
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
333
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
334
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Sample script that show basic usage of Net::Ops.
|
2
|
+
# For more information refer to the documentation.
|
3
|
+
# To generate it run `yardoc lib/net/ops/session.rb`.
|
4
|
+
|
5
|
+
# Add lib/ to the PATH.
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[lib])
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'net/ops'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
# Load credentials from credentials.yml.
|
13
|
+
#
|
14
|
+
# File format is :
|
15
|
+
# username: myusername
|
16
|
+
# password: mypassword
|
17
|
+
credentials = YAML.load_file('credentials.yml')
|
18
|
+
|
19
|
+
# Define the timeout and the prompt (optional).
|
20
|
+
# Net::Ops::Session.new default to
|
21
|
+
# { timeout: 10, prompt: /.+(#|>)/ }
|
22
|
+
options = { timeout: 10, prompt: /.+(#|>)/ }
|
23
|
+
|
24
|
+
# Create a logger (optional).
|
25
|
+
# Net::Ops::Session.setup_logger default to
|
26
|
+
# logger = Logger.new(STDOUT)
|
27
|
+
# logger.level = Logger::DEBUG
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::DEBUG
|
30
|
+
|
31
|
+
# Create a session to sa-qcmtlv-11-09 (1).
|
32
|
+
# This is the exhaustive form with all optionals params.
|
33
|
+
session = Net::Ops::Session.new('sa-qcmtlv-11-09', options, logger)
|
34
|
+
|
35
|
+
# OR
|
36
|
+
|
37
|
+
# Create a session to sa-qcmtlv-11-09 (2).
|
38
|
+
# This is the short form without the optional params.
|
39
|
+
# See the documentation/code for default values.
|
40
|
+
session = Net::Ops::Session.new('sa-qcmtlv-11-09')
|
41
|
+
|
42
|
+
# Open the session using the specified credentials.
|
43
|
+
# This form provides the full hash in case
|
44
|
+
# key names are not :username and :password.
|
45
|
+
# Or if you want to specify credentials directly.
|
46
|
+
session.open({ username: credentials.fetch('username'),
|
47
|
+
password: credentials.fetch('password') })
|
48
|
+
|
49
|
+
# Set terminal length to 0 otherwise too long outputs will cause
|
50
|
+
# Net::Telnet to timeout while waiting for the prompt.
|
51
|
+
session.privileged { set 'terminal length', 0 }
|
52
|
+
|
53
|
+
# Here we pass a block to be executed in the privileged mode.
|
54
|
+
session.privileged do
|
55
|
+
|
56
|
+
# Let's get interfaces status.
|
57
|
+
sw_interfaces = get 'interfaces status'
|
58
|
+
|
59
|
+
# Show disabled interfaces
|
60
|
+
@nc_interfaces = sw_interfaces.select { |int| int['status'] == 'disabled' }
|
61
|
+
puts @nc_interfaces
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# Do some stuff in configuration mode.
|
66
|
+
session.configuration do
|
67
|
+
|
68
|
+
# Add description to Gi1/0/2.
|
69
|
+
# Note the singular/plural in interface(s).
|
70
|
+
# interface accept only String as an argument.
|
71
|
+
# interfaces accept Array, Regexp, and String.
|
72
|
+
interface('Gi1/0/2') do
|
73
|
+
set 'description', 'I am Gi1/0/2'
|
74
|
+
end
|
75
|
+
|
76
|
+
# Disable bpduguard on all Gig interfaces.
|
77
|
+
interfaces(/Gi1\/0/) do
|
78
|
+
disable 'spanning-tree bpduguard'
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# Copy to startup-config
|
84
|
+
session.write!
|
85
|
+
|
86
|
+
# Do something else in configuration mode
|
87
|
+
# but automatically write this time.
|
88
|
+
session.configuration(:enforce_save) do
|
89
|
+
disable 'ip http secure-server'
|
90
|
+
end
|
91
|
+
|
92
|
+
# Close the session.
|
93
|
+
# Optionnal since Ruby garbage collector should do that for us.
|
94
|
+
session.close
|