Skip to content
This repository has been archived by the owner on Dec 16, 2020. It is now read-only.

Commit

Permalink
Merge branch 'staging'
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Gauld committed Jun 3, 2017
2 parents f91a242 + 4a839a0 commit 9ca73ca
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,8 @@
## Version 0.0.5

* Rename snmp-table-viewer script to table-from-snmp
* Add script table-from-stdin

## Version 0.0.4

* Rename gem to snmp_table_viewer
Expand Down
18 changes: 16 additions & 2 deletions README.md
Expand Up @@ -40,10 +40,11 @@ gem 'snmp_table_viewer', '~> 0.0'
```


## Executable
## Executables

### Create table from doing an SNMP walk
```
Usage: snmp-table-viewer [options]
Usage: table-from-snmp [options]
-h, -?, --help Prints this help.
--headings HEADINGS Headings to use in the table (e.g. "A, Bc, D").
--headings-for TYPE Use headings for this table type (ifTable).
Expand Down Expand Up @@ -75,6 +76,19 @@ SNMP version 3 options:
Table formatter options:
--[no-]transpose-table Transpose the output table (swap rows and columns).
```
### Create table from piped in data
```
Usage: table-from-stdin [options]
-h, -?, --help Prints this help.
--headings HEADINGS Headings to use in the table (e.g. "A, Bc, D").
--headings-for TYPE Use headings for this table type (ifTable).
--format FORMAT How to format the output (table|csv|json|raw) (default table).
--converter CONVERTER A converter to run on the data before formatting (iftable).
Table formatter options:
--[no-]transpose-table Transpose the output table (swap rows and columns).
```


## Documentation & Versioning

Expand Down
33 changes: 9 additions & 24 deletions bin/snmp-table-viewer → bin/table-from-snmp
Expand Up @@ -29,25 +29,10 @@ def parse_command_line
headings: [],
formatter: SNMPTableViewer::Formatter::Table,
}
headings_for = {
'ifTable' => ['Index', 'Descr', 'Type', 'Mtu', 'Speed', 'PhysAddress', 'AdminStatus', 'OperStatus', 'LastChange', 'InOctets', 'InUcastPkts', 'InNUcastkts', 'InDiscards', 'InErrors', 'InUnknownPrortos', 'OutOctets', 'OutUcastPkts', 'OutNUcastPkts', 'OutDiscards', 'OutErrors', 'OutQLen', 'Specific'],
}
formatters = {
'table' => SNMPTableViewer::Formatter::Table,
'csv' => SNMPTableViewer::Formatter::CSV,
'json' => SNMPTableViewer::Formatter::JSON,
'raw' => SNMPTableViewer::Formatter::Raw,
}
converters = {
'iftable' => SNMPTableViewer::Converter::IfTable,
}
base_oids = {
'iftable' => '1.3.6.1.2.1.2.2',
}
snmp_version = 'v3'

parser = OptionParser.new do |opts|
opts.banner = "Usage: snmp-table-viewer [options]"
opts.banner = "Usage: table-from-snmp [options]"

opts.on('-h', '--help', '-?', 'Prints this help.') do
puts opts
Expand All @@ -58,16 +43,16 @@ def parse_command_line
options[:headings] += v.map(&:strip)
end

opts.on('--headings-for TYPE', String, "Use headings for this table type (#{headings_for.keys.join('|')}).") do |v|
options[:headings] += headings_for[v] || []
opts.on('--headings-for TYPE', String, "Use headings for this table type (#{SNMPTableViewer::HEADINGS_FOR.keys.join('|')}).") do |v|
options[:headings] += SNMPTableViewer::HEADINGS_FOR[v] || []
end

opts.on('--format FORMAT', String, "How to format the output (#{formatters.keys.join('|')}) (default table).") do |v|
options[:formatter] = formatters[v.downcase]
opts.on('--format FORMAT', String, "How to format the output (#{SNMPTableViewer::FORMATTERS.keys.join('|')}) (default table).") do |v|
options[:formatter] = SNMPTableViewer::FORMATTERS[v.downcase]
end

opts.on('--converter CONVERTER', String, "A converter to run on the data before formatting (#{converters.keys.join('|')}).") do |v|
options[:converter] = converters[v.downcase]
opts.on('--converter CONVERTER', String, "A converter to run on the data before formatting (#{SNMPTableViewer::CONVERTERS.keys.join('|')}).") do |v|
options[:converter] = SNMPTableViewer::CONVERTERS[v.downcase]
end


Expand All @@ -87,7 +72,7 @@ def parse_command_line
end

opts.on('--base-oid OID', 'The oid at the start of the table. Can by dotted numbers or ifTable') do |v|
options[:base_oid] = base_oids[v.downcase] || v
options[:base_oid] = SNMPTableViewer::BASE_OIDS[v.downcase] || v
end


Expand Down Expand Up @@ -139,7 +124,7 @@ options = parse_command_line
snmp_options = options[:snmp][:common].clone
snmp_options.merge!(options[:snmp][snmp_options[:version]])

data = SNMPTableViewer::Fetcher.fetch(base_oid: options[:base_oid], **snmp_options)
data = SNMPTableViewer::Fetcher.from_snmp(base_oid: options[:base_oid], **snmp_options)

converter = options[:converter]
data = converter.convert(data) unless converter.nil?
Expand Down
62 changes: 62 additions & 0 deletions bin/table-from-stdin
@@ -0,0 +1,62 @@
#!/usr/bin/env ruby

require 'optparse'
require_relative File.join('..', 'lib', 'snmp_table_viewer')

def parse_command_line
options = {
SNMPTableViewer::Formatter::Table => {
transpose: false,
},
headings: [],
formatter: SNMPTableViewer::Formatter::Table,
}

parser = OptionParser.new do |opts|
opts.banner = "Usage: table-from-stdin [options]"

opts.on('-h', '--help', '-?', 'Prints this help.') do
puts opts
exit
end

opts.on('--headings HEADINGS', Array, 'Headings to use in the table (e.g. "A, Bc, D").') do |v|
options[:headings] += v.map(&:strip)
end

opts.on('--headings-for TYPE', String, "Use headings for this table type (#{SNMPTableViewer::HEADINGS_FOR.keys.join('|')}).") do |v|
options[:headings] += SNMPTableViewer::HEADINGS_FOR[v] || []
end

opts.on('--format FORMAT', String, "How to format the output (#{SNMPTableViewer::FORMATTERS.keys.join('|')}) (default table).") do |v|
options[:formatter] = SNMPTableViewer::FORMATTERS[v.downcase]
end

opts.on('--converter CONVERTER', String, "A converter to run on the data before formatting (#{SNMPTableViewer::CONVERTERS.keys.join('|')}).") do |v|
options[:converter] = SNMPTableViewer::CONVERTERS[v.downcase]
end


opts.separator "\nTable formatter options:"
opts.on('--[no-]transpose-table', 'Transpose the output table (swap rows and columns).') do |v|
options[SNMPTableViewer::Formatter::Table][:transpose] = v
end

end # create parser

parser.parse!(ARGV)
options
end


options = parse_command_line
data = STDIN.readlines.map(&:strip)
data = SNMPTableViewer::Fetcher.from_array(data)

converter = options[:converter]
data = converter.convert(data) unless converter.nil?

formatter = options[:formatter]
formatter_options = options[formatter] || {}
formatter = formatter.new(data: data, headings: options[:headings])
puts formatter.output(**formatter_options)
20 changes: 20 additions & 0 deletions lib/snmp_table_viewer.rb
Expand Up @@ -11,4 +11,24 @@
Dir["#{File.dirname(__FILE__)}/**/*.rb"].select{ |f| f != __FILE__}.sort.each { |f| load(f) }

module SNMPTableViewer

HEADINGS_FOR = {
'ifTable' => ['Index', 'Descr', 'Type', 'Mtu', 'Speed', 'PhysAddress', 'AdminStatus', 'OperStatus', 'LastChange', 'InOctets', 'InUcastPkts', 'InNUcastkts', 'InDiscards', 'InErrors', 'InUnknownPrortos', 'OutOctets', 'OutUcastPkts', 'OutNUcastPkts', 'OutDiscards', 'OutErrors', 'OutQLen', 'Specific'],
}.freeze

FORMATTERS = {
'table' => SNMPTableViewer::Formatter::Table,
'csv' => SNMPTableViewer::Formatter::CSV,
'json' => SNMPTableViewer::Formatter::JSON,
'raw' => SNMPTableViewer::Formatter::Raw,
}.freeze

CONVERTERS = {
'iftable' => SNMPTableViewer::Converter::IfTable,
}.freeze

BASE_OIDS = {
'iftable' => '1.3.6.1.2.1.2.2',
}.freeze

end # module SNMPTableViewer
48 changes: 46 additions & 2 deletions lib/snmp_table_viewer/fetcher.rb
Expand Up @@ -6,19 +6,63 @@ class Fetcher
# Fetch the data using SNMP.
# @param base_oid [String] The OID to start the SNMP walk from
# @param **snmp_options [Hash] The options to pass to NETSNMP::Client.new
# @return [Array<Array<#to_s>>] A two dimensional array containing objects in each cell (at 'address' data[row][col])
# @return [Array<Array<#to_s>>] A two dimensional array containing objects in each cell (at 'address' data\[row\]\[col\])
def self.from_snmp(base_oid:, **snmp_options)
data = Array.new
NETSNMP::Client.new(snmp_options) do |manager|
manager.walk(oid: base_oid).each do |oid, value|
col, row = oid.split('.')[-2..-1].map{ |i| i.to_i - 1}
data[row] ||= []
data[row] ||= Array.new
data[row][col] = value
end
end
data
end

# Build the data from an Array<String>.
# Each String of the format returned by the snmpwalk command ("<oid> = <type>: <value>").
# @param data [Array<String>] The Strings to get the data from
# @return [Array<Array<#to_s>>] A two dimensional array containing objects in each cell (at 'address' data\[row\]\[col\])
def self.from_array(data_in)
data_out = Array.new
regexp_general = Regexp.compile(/\A(?:iso)?[\.0-9]+\.([0-9]+)\.([0-9]+)\s+=\s+([A-Za-z0-9 \-]+):\s+(.+)\Z/)
regexp_nodata = Regexp.compile(/\A(?:iso)?[\.0-9]+\.([0-9]+)\.([0-9]+)\s+=\s+""\Z/)
data_in.each.with_index do |line, index|
# Try to get a match on the various valid frmats of line
match = line.match(regexp_general)
match ||= line.match(regexp_nodata)
if match.nil?
STDERR.puts "Could not parse data on line #{index+1}: #{line}"
next
end

col, row, type, value = match.captures
# Convert value
case type && type.downcase
when nil
when 'string'
value = value[1..-2] # Strip enclosing quotes
when 'oid', 'hex-string'
when 'integer', 'integer32', 'uinteger32', 'gauge32', 'counter32', 'counter64'
value = value.to_i
when 'ipaddress'
value = IPAddr.new(value)
when 'timeticks'
match = value.match(/\A\((\d+)\)/)
value = NETSNMP::Timetick.new(match.nil? ? 0 : match[1].to_i)
else
STDERR.puts "Unknown SNMP type (#{type}) on line #{index+1}: #{line}"
end

# Save value
row = row.to_i - 1
col = col.to_i - 1
data_out[row] ||= Array.new
data_out[row][col] = value
end # each line of data_in
data_out
end

end # class Fetcher

end # module SNMPTableViewer
9 changes: 5 additions & 4 deletions snmp_table_viewer.gemspec
@@ -1,21 +1,22 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
$:.push File.expand_path(File.join('..', 'lib'), __FILE__)
require File.join(File.dirname(__FILE__), 'version')

Gem::Specification.new do |s|
s.name = "snmp_table_viewer"
s.name = 'snmp_table_viewer'
s.license = 'BSD 3 clause'
s.version = SNMPTableViewer::VERSION
s.authors = ['Robert Gauld']
s.email = ['robert@robertgauld.co.uk']
s.homepage = 'https://github.com/robertgauld/snmp-table-viewer'
s.homepage = 'https://github.com/robertgauld/snmp_table_viewer'
s.summary = %q{Easily view SNMP tables.}
s.description = %q{Easily view SNMP tables in a variety of different formats including as a table in the terminal, json or csv.}

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.require_paths = ['lib']
s.bindir = 'bin'

s.add_runtime_dependency 'netsnmp', '~> 0.1.3'
s.add_runtime_dependency 'terminal-table', '~> 1.8.0'
Expand Down
44 changes: 43 additions & 1 deletion spec/snmp_table_viewer/fetcher_spec.rb
@@ -1,6 +1,6 @@
describe SNMPTableViewer::Fetcher do

it 'Fetches' do
it '#from_snmp' do
snmp_options = {opt_a: :a}
manager = double("manager")
expect(manager).to receive(:walk).with(oid: '1.2.3.4.5').once.and_return([
Expand All @@ -20,4 +20,46 @@
]
end

describe '#from_array' do
it 'Given array of strings "<oid> = <type>: <value>"' do
data = [
'iso.3.6.1.2.1.2.2.1.1.1 = INTEGER: 1',
'iso.3.6.1.2.1.2.2.1.2.1 = STRING: "lo"',
'iso.3.6.1.2.1.2.2.1.3.1 = Gauge32: 2',
'iso.3.6.1.2.1.2.2.1.4.1 = Hex-STRING: 01 23 45 67 89 AB',
'iso.3.6.1.2.1.2.2.1.5.1 = Timeticks: (10) 0:00:10.00',
'iso.3.6.1.2.1.2.2.1.6.1 = Counter32: 3',
'iso.3.6.1.2.1.2.2.1.7.1 = OID: ccitt.0',
'iso.3.6.1.2.1.2.2.1.8.1 = IpAddress: 1.2.3.4',
'iso.3.6.1.2.1.2.2.1.10.1 = ""',
]
expect(described_class.from_array(data)).to eq [[
1,
'lo',
2,
'01 23 45 67 89 AB',
NETSNMP::Timetick.new(10),
3,
'ccitt.0',
IPAddr.new('1.2.3.4'),
nil,
nil
]]
end

it 'Errors but survives on a bad line' do
data = ['This is a bad line', 'This is another bad line']
expect(STDERR).to receive(:puts).with('Could not parse data on line 1: This is a bad line').once
expect(STDERR).to receive(:puts).with('Could not parse data on line 2: This is another bad line').once
expect(described_class.from_array(data)).to eq []
end

it 'Errors but survives on a bad type' do
data = ['1.2.3.4.0.1.1 = INTEGER: 1', '1.2.3.4.0.2.1 = invalid-type: 1']
expect(STDERR).to receive(:puts).with('Unknown SNMP type (invalid-type) on line 2: 1.2.3.4.0.2.1 = invalid-type: 1').once
expect(described_class.from_array(data)).to eq [[1, '1']]
end

end # describe #from_array

end
2 changes: 1 addition & 1 deletion version.rb
@@ -1,3 +1,3 @@
module SNMPTableViewer
VERSION = "0.0.4"
VERSION = "0.0.5"
end

0 comments on commit 9ca73ca

Please sign in to comment.