snmp-open 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db7e18f61349f692d1543dcfe02ae4e2bc9bab34
4
- data.tar.gz: 37598b9ab5c8797bd2227eac71da7e8e945ddca7
3
+ metadata.gz: ed0ba9db497fbb007a69e702a2ed6321cfde3583
4
+ data.tar.gz: 9cbd977f12ae09f800fc0b27aa29fbad04bd0cd9
5
5
  SHA512:
6
- metadata.gz: f7dc1bf3650a77fcce40e30ed2c9d6a7ac4f248e91adfac6e1358f5455e06cb045f44be5d9167287a9de4aef13843147a14f41e6b5cf962840deef5f8ae57a09
7
- data.tar.gz: cf03e2bb2dc6509afe2991e8f0aaf02a4a262cefd0bc1ca733da93024d8006272177126e223aa861aaffda6ba750596bac90bb96a721fbde6d3467c919b173fe
6
+ metadata.gz: 74fb9d39823597e02d95f2029e14391d16851e29f0b20006ab595550e56207dc2f20a1bb3404aa118d25cf9e0a8016542c60be327bf43e45683bad890571aa7a
7
+ data.tar.gz: 6b29974a7a28fa197acb75647674f036f7ef5fad733de2f0ae7a5c780005717e17d8cb87fb4855f398768665ccf987432906d6ed6131f69546798725ac98f6f0
@@ -1,39 +1,13 @@
1
1
  require 'open3'
2
+ require 'snmp/open/options'
2
3
 
3
4
  module SNMP
4
5
  class Open
5
6
  # Open3-based data source that executes an snmp* command and captures the
6
7
  # output
7
8
  class CommandReader
8
- # see snmpcmd(1) for explanation of options
9
- OPTIONS = {
10
- version: '-v',
11
- auth_password: '-A',
12
- auth_protocol: '-a',
13
- community: '-c',
14
- context: '-n',
15
- no_check_increasing: {
16
- 'snmpbulkwalk' => '-Cc',
17
- 'snmpwalk' => '-Cc'
18
- },
19
- numeric: '-On', # needed by parser, should always be enabled
20
- priv_password: '-X', # not recommended, see snmp.conf(5)
21
- priv_protocol: '-x',
22
- sec_level: '-l',
23
- sec_user: '-u',
24
- retries: '-r',
25
- timeout: '-t',
26
- host: nil
27
- }.freeze
28
-
29
- OPTION_VALUES = {
30
- no_check_increasing: {
31
- true => ''
32
- }.freeze
33
- }.freeze
34
-
35
9
  # +options+ accepts options dealing with making connections to the host,
36
- # including all of the options listed in the +OPTIONS+ constant hash.
10
+ # including all of the options listed in the +Options::MAP+ constant hash.
37
11
  # Other options can be given as strings (or any object with a suitable
38
12
  # +to_s+ method), e.g., these are equivalent:
39
13
  #
@@ -44,7 +18,9 @@ module SNMP
44
18
  @env = options.delete(:env)
45
19
  host = options.delete(:host) ||
46
20
  (raise ArgumentError, 'Host expected but not given')
47
- opts = merge_options(options).merge('-On' => nil, host => nil)
21
+ opts = Options::REQUIRED_BY_PARSER
22
+ .merge(merge_options(options))
23
+ .merge(host => nil)
48
24
  @command_options, @host_options = partition_options(opts)
49
25
  end
50
26
 
@@ -76,9 +52,9 @@ module SNMP
76
52
 
77
53
  def merge_options(options = {})
78
54
  options.each_pair.with_object({}) do |(key, value), opts|
79
- if OPTIONS.key?(key)
80
- opts[OPTIONS[key]] =
81
- (OPTION_VALUES.fetch(key, {}).fetch(value, value) || next)
55
+ if Options::MAP.key?(key)
56
+ opts[Options::MAP[key]] =
57
+ (Options::VALUES.fetch(key, {}).fetch(value, value) || next)
82
58
  elsif key.is_a?(String)
83
59
  opts[key] = value
84
60
  else
@@ -0,0 +1,43 @@
1
+ module SNMP
2
+ class Open
3
+ class Options
4
+ # see snmpcmd(1) for explanation of options
5
+ MAP = {
6
+ version: '-v',
7
+ auth_password: '-A',
8
+ auth_protocol: '-a',
9
+ community: '-c',
10
+ context: '-n',
11
+ no_check_increasing: {
12
+ 'snmpbulkwalk' => '-Cc',
13
+ 'snmpwalk' => '-Cc'
14
+ },
15
+ no_units: '-OU',
16
+ non_symbolic: '-Oe',
17
+ numeric: '-On',
18
+ priv_password: '-X', # not recommended, see snmp.conf(5)
19
+ priv_protocol: '-x',
20
+ sec_level: '-l',
21
+ sec_user: '-u',
22
+ retries: '-r',
23
+ timeout: '-t',
24
+ host: nil
25
+ }.freeze
26
+
27
+ # On some systems, SNMP command outputs will include symbolic OID names,
28
+ # symbolic values, and/or value units. The parser doesn't support these,
29
+ # so disable them.
30
+ REQUIRED_BY_PARSER = {
31
+ '-Oe' => nil,
32
+ '-On' => nil,
33
+ '-OU' => nil
34
+ }.freeze
35
+
36
+ VALUES = {
37
+ no_check_increasing: {
38
+ true => ''
39
+ }.freeze
40
+ }.freeze
41
+ end # class Options
42
+ end # class Open
43
+ end # module SNMP
@@ -1,5 +1,7 @@
1
1
  require 'set'
2
2
  require 'shellwords'
3
+ require 'snmp/open/parser/constants'
4
+ require 'snmp/open/parser/value_parser'
3
5
 
4
6
  module SNMP
5
7
  class Open
@@ -11,10 +13,7 @@ module SNMP
11
13
 
12
14
  # convert SNMP command output into arrays
13
15
  class Parser
14
- NOSUCHOBJECT_STR =
15
- 'No Such Object available on this agent at this OID'.freeze
16
- NOSUCHINSTANCE_STR =
17
- 'No Such Instance currently exists at this OID'.freeze
16
+ include SNMP::Open::Parser::Constants
18
17
 
19
18
  def initialize(oids)
20
19
  @oids = oids
@@ -24,8 +23,9 @@ module SNMP
24
23
  columns = texts.map do |text|
25
24
  tokenized =
26
25
  text
27
- .gsub(NOSUCHOBJECT_STR, %("#{NOSUCHOBJECT_STR}"))
28
- .gsub(NOSUCHINSTANCE_STR, %("#{NOSUCHINSTANCE_STR}"))
26
+ .gsub(/^([0-9\.]+) = (Opaque|STRING): ((?!")[^\n]*)\n/,
27
+ %(\\1 = \\2: "\\3"\n))
28
+ .gsub(Static::ANY_MESSAGE, Static::QUOTED_MESSAGES)
29
29
  .shellsplit
30
30
  parse_tokens(tokenized)
31
31
  end
@@ -82,22 +82,9 @@ module SNMP
82
82
  end
83
83
 
84
84
  def parse_type(tokens)
85
- next_token = tokens.next
86
- type = next_token.match(/\A([A-Z]+):\z/) { |md| md[1] }
87
- type, value = parse_value(tokens, next_token, type)
88
- [type, Conversions.convert_value(type, value)]
89
- end
90
-
91
- def parse_value(tokens, token, type)
92
- if token == NOSUCHOBJECT_STR
93
- ['No Such Object', nil]
94
- elsif token == NOSUCHINSTANCE_STR
95
- ['No Such Instance', nil]
96
- elsif !type
97
- ['STRING', token]
98
- else
99
- [type, tokens.next]
100
- end
85
+ token = tokens.next
86
+ type = token.match(/\A([-A-Za-z]+[0-9]*):\z/) { |md| md[1] }
87
+ ValueParser.find(type, token).parse(tokens)
101
88
  end
102
89
 
103
90
  def table(columns)
@@ -124,25 +111,24 @@ module SNMP
124
111
  @oids.zip(columns).map do |oid, column|
125
112
  indexes.map do |index|
126
113
  id = (oid == index ? index : "#{oid}.#{index}")
127
- column.find { |o| o.oid == id } || Conversions.absent_value(id)
114
+ column.find { |o| o.oid == id } || Value.new(id, 'absent', nil)
128
115
  end
129
116
  end
130
117
  end
131
118
 
132
- # functions to generate value objects
133
- module Conversions
134
- module_function def convert_value(type, value)
135
- case type
136
- when 'INTEGER'
137
- value.to_i
138
- else
139
- value
140
- end
141
- end
119
+ # static messages from net-snmp commands
120
+ module Static
121
+ include SNMP::Open::Parser::Constants
142
122
 
143
- module_function def absent_value(id)
144
- Value.new(id, 'absent', nil)
145
- end
123
+ MESSAGES = [
124
+ NOSUCHOBJECT_STR,
125
+ NOSUCHINSTANCE_STR,
126
+ NOMOREVARIABLES_STR
127
+ ].freeze
128
+
129
+ ANY_MESSAGE = Regexp.union(*MESSAGES)
130
+
131
+ QUOTED_MESSAGES = MESSAGES.map { |v| [v, %("#{v}")] }.to_h.freeze
146
132
  end
147
133
  end # class Parser
148
134
  end # class Open
@@ -0,0 +1,15 @@
1
+ module SNMP
2
+ class Open
3
+ class Parser
4
+ module Constants
5
+ NOSUCHOBJECT_STR =
6
+ 'No Such Object available on this agent at this OID'.freeze
7
+ NOSUCHINSTANCE_STR =
8
+ 'No Such Instance currently exists at this OID'.freeze
9
+ NOMOREVARIABLES_STR =
10
+ 'No more variables left in this MIB View '\
11
+ '(It is past the end of the MIB tree)'.freeze
12
+ end # module Constants
13
+ end # class Parser
14
+ end # class Open
15
+ end # module SNMP
@@ -0,0 +1,127 @@
1
+ module SNMP
2
+ class Open
3
+ class Parser
4
+ # base class for value parsers
5
+ class ValueParser
6
+ include SNMP::Open::Parser::Constants
7
+
8
+ def self.find(type, token)
9
+ cls = KNOWN_TOKENS[token] || KNOWN_TYPES[type] || Other
10
+ cls.new(type, token)
11
+ end
12
+
13
+ def initialize(type, token)
14
+ @type = type
15
+ @token = token
16
+ end
17
+
18
+ def parse(*)
19
+ @parse
20
+ end
21
+
22
+ # parses BITS
23
+ class Bits < ValueParser
24
+ def parse(tokens)
25
+ return @parse if @parse
26
+ bytes = []
27
+ loop do
28
+ break unless tokens.peek =~ /\A[0-9A-Za-z]{1,2}\z/
29
+ bytes << tokens.next.to_i(16)
30
+ end
31
+ @parse = [@type, bytes]
32
+ end
33
+ end # class Bits < ValueParser
34
+
35
+ # parses objects with no explicit type
36
+ class Default < ValueParser
37
+ def initialize(_type, token)
38
+ @parse = ['STRING', token]
39
+ end
40
+ end # class Default
41
+
42
+ # parses integer-like objects
43
+ class Integer < ValueParser
44
+ def parse(tokens)
45
+ @parse ||= [@type, Integer(tokens.next)]
46
+ end
47
+ end
48
+
49
+ # parses objects identified like '= Hex-STRING:'
50
+ class HexString < ValueParser
51
+ def parse(tokens)
52
+ return @parse if @parse
53
+ bytes = []
54
+ loop do
55
+ break unless tokens.peek =~ /\A[0-9A-Za-z]{2}\z/
56
+ bytes << tokens.next
57
+ end
58
+ string = bytes.map { |b| b.to_i(16).chr }.join
59
+ @parse = [@type, string]
60
+ end
61
+ end # class HexString
62
+
63
+ # handles messages indicating the end of the response
64
+ class Stop < ValueParser
65
+ def parse(*)
66
+ raise StopIteration, @token
67
+ end
68
+ end
69
+
70
+ # parses objects identified like '= Timeticks:'
71
+ # note that 1 second = 100 ticks
72
+ class Timeticks < ValueParser
73
+ def parse(tokens)
74
+ return @parse if @parse
75
+ ticks = tokens.next.tr('()', '').to_i
76
+
77
+ # consume tokens through one like 23:59:59.99
78
+ loop do
79
+ break if tokens.next =~ /\A\d\d:\d\d:\d\d.\d\d\z/
80
+ end
81
+
82
+ @parse = [@type, ticks]
83
+ end
84
+ end # class Timeticks
85
+
86
+ # handles objects not handled by any other parser
87
+ class Other < ValueParser
88
+ def parse(tokens)
89
+ @parse ||= [@type, tokens.next]
90
+ end
91
+ end # class Other
92
+
93
+ # handles NoSuchInstance
94
+ class NoSuchInstance < ValueParser
95
+ def initialize(*)
96
+ @parse = ['No Such Instance', nil]
97
+ end
98
+ end # class NoSuchInstance < ValueParser
99
+
100
+ # handles NoSuchObject
101
+ class NoSuchObject < ValueParser
102
+ def initialize(*)
103
+ @parse = ['No Such Object', nil]
104
+ end
105
+ end # class NoSuchObject < ValueParser
106
+
107
+ KNOWN_TOKENS = {
108
+ NOSUCHINSTANCE_STR => NoSuchInstance,
109
+ NOSUCHOBJECT_STR => NoSuchObject,
110
+ NOMOREVARIABLES_STR => Stop
111
+ }.freeze
112
+
113
+ KNOWN_TYPES = {
114
+ nil => Default,
115
+ 'BITS' => Bits,
116
+ 'INTEGER' => ValueParser::Integer,
117
+ 'Gauge32' => ValueParser::Integer,
118
+ 'Gauge64' => ValueParser::Integer,
119
+ 'Counter32' => ValueParser::Integer,
120
+ 'Counter64' => ValueParser::Integer,
121
+ 'Hex-STRING' => HexString,
122
+ 'Timeticks' => Timeticks
123
+ }.freeze
124
+ end # class ValueParser
125
+ end # class Parser
126
+ end # class Open
127
+ end # module SNMP
@@ -1,5 +1,5 @@
1
- module Snmp
1
+ module SNMP
2
2
  module Open
3
- VERSION = '0.3.1'.freeze
3
+ VERSION = '0.4.0'.freeze
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ require 'snmp/open/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'snmp-open'
7
- spec.version = Snmp::Open::VERSION
7
+ spec.version = SNMP::Open::VERSION
8
8
  spec.authors = ['Ben Miller']
9
9
  spec.email = ['bmiller@rackspace.com']
10
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snmp-open
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Miller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-28 00:00:00.000000000 Z
11
+ date: 2017-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -89,7 +89,10 @@ files:
89
89
  - lib/snmp/open.rb
90
90
  - lib/snmp/open/command_reader.rb
91
91
  - lib/snmp/open/file_reader.rb
92
+ - lib/snmp/open/options.rb
92
93
  - lib/snmp/open/parser.rb
94
+ - lib/snmp/open/parser/constants.rb
95
+ - lib/snmp/open/parser/value_parser.rb
93
96
  - lib/snmp/open/version.rb
94
97
  - snmp-open.gemspec
95
98
  homepage: https://github.com/bjmllr/snmp-open