glazed 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE.txt +202 -0
- data/NOTICE +16 -0
- data/README.md +3 -0
- data/Rakefile +12 -0
- data/exe/glazed +8 -0
- data/lib/glazed/cli_session.rb +178 -0
- data/lib/glazed/collector.rb +113 -0
- data/lib/glazed/command/base.rb +146 -0
- data/lib/glazed/command/orchestrate.rb +78 -0
- data/lib/glazed/command/run.rb +51 -0
- data/lib/glazed/command/send_signal.rb +69 -0
- data/lib/glazed/command/status.rb +179 -0
- data/lib/glazed/command/version.rb +23 -0
- data/lib/glazed/command.rb +39 -0
- data/lib/glazed/command_result.rb +26 -0
- data/lib/glazed/dialect/actions.rb +95 -0
- data/lib/glazed/dialect/communication_config.rb +63 -0
- data/lib/glazed/dialect/interaction.rb +32 -0
- data/lib/glazed/dialect/run.rb +84 -0
- data/lib/glazed/dialect/runbook.rb +97 -0
- data/lib/glazed/dialect/sensitive_pattern.rb +31 -0
- data/lib/glazed/dialect.rb +12 -0
- data/lib/glazed/dialects/awplus.rb +84 -0
- data/lib/glazed/dialects/base.rb +246 -0
- data/lib/glazed/dialects/ios.rb +173 -0
- data/lib/glazed/dialects/junos.rb +78 -0
- data/lib/glazed/dialects/nec_ix.rb +71 -0
- data/lib/glazed/dialects/wlc.rb +56 -0
- data/lib/glazed/dialects.rb +65 -0
- data/lib/glazed/finder.rb +62 -0
- data/lib/glazed/must.rb +54 -0
- data/lib/glazed/orchestrator/policy.rb +184 -0
- data/lib/glazed/orchestrator.rb +288 -0
- data/lib/glazed/plugin_registry.rb +70 -0
- data/lib/glazed/proto.rb +20 -0
- data/lib/glazed/proto_json.rb +34 -0
- data/lib/glazed/redactor.rb +158 -0
- data/lib/glazed/runner.rb +176 -0
- data/lib/glazed/secret.rb +74 -0
- data/lib/glazed/secrets.rb +59 -0
- data/lib/glazed/shell_command.rb +90 -0
- data/lib/glazed/sources/base.rb +79 -0
- data/lib/glazed/sources/command.rb +67 -0
- data/lib/glazed/sources/inline.rb +56 -0
- data/lib/glazed/sources/json_files.rb +87 -0
- data/lib/glazed/sources.rb +105 -0
- data/lib/glazed/state_backends/base.rb +72 -0
- data/lib/glazed/state_backends/sqlite.rb +299 -0
- data/lib/glazed/state_backends.rb +75 -0
- data/lib/glazed/step_context.rb +65 -0
- data/lib/glazed/steps/base.rb +89 -0
- data/lib/glazed/steps/batch_shell.rb +57 -0
- data/lib/glazed/steps/fetch.rb +67 -0
- data/lib/glazed/steps/publish.rb +94 -0
- data/lib/glazed/steps/redact.rb +77 -0
- data/lib/glazed/steps/shell.rb +107 -0
- data/lib/glazed/steps.rb +83 -0
- data/lib/glazed/storages/base.rb +167 -0
- data/lib/glazed/storages/filesystem.rb +144 -0
- data/lib/glazed/storages/git.rb +181 -0
- data/lib/glazed/storages/github.rb +269 -0
- data/lib/glazed/storages/memory.rb +125 -0
- data/lib/glazed/storages.rb +88 -0
- data/lib/glazed/transports/base.rb +103 -0
- data/lib/glazed/transports/ssh.rb +177 -0
- data/lib/glazed/transports/telnet.rb +100 -0
- data/lib/glazed/transports.rb +28 -0
- data/lib/glazed/v1/config_pb.rb +25 -0
- data/lib/glazed/v1/inventory_pb.rb +27 -0
- data/lib/glazed/v1/orchestrator_pb.rb +27 -0
- data/lib/glazed/v1/redaction_pb.rb +23 -0
- data/lib/glazed/v1/secret_pb.rb +22 -0
- data/lib/glazed/v1/state_pb.rb +25 -0
- data/lib/glazed/v1/steps_pb.rb +25 -0
- data/lib/glazed/v1/storage_pb.rb +34 -0
- data/lib/glazed/v1/workflow_pb.rb +29 -0
- data/lib/glazed/validation.rb +31 -0
- data/lib/glazed/version.rb +9 -0
- data/lib/glazed/workflow.rb +70 -0
- data/lib/glazed.rb +18 -0
- metadata +245 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
require 'glazed/dialect'
|
|
7
|
+
require 'glazed/dialect/actions'
|
|
8
|
+
require 'glazed/dialect/communication_config'
|
|
9
|
+
require 'glazed/dialect/interaction'
|
|
10
|
+
require 'glazed/dialect/runbook'
|
|
11
|
+
require 'glazed/dialect/sensitive_pattern'
|
|
12
|
+
|
|
13
|
+
module Glazed
|
|
14
|
+
module Dialects
|
|
15
|
+
# Base class of dialects. A dialect describes how to talk to one kind of
|
|
16
|
+
# device and how to collect its configuration, through class-level
|
|
17
|
+
# declarations that subclasses inherit.
|
|
18
|
+
#
|
|
19
|
+
# Lists (data and output filters, interactions, sensitive patterns,
|
|
20
|
+
# communication blocks) accumulate from parent to child. The prompt, comment
|
|
21
|
+
# prefix and each runbook are replaced by a child's own declaration.
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# class Rootware < Glazed::Dialects::Base
|
|
25
|
+
# prompt(/^\S+[#>]\s?$/)
|
|
26
|
+
# comment_prefix '! '
|
|
27
|
+
# runbook :get do |book|
|
|
28
|
+
# book.command('show version') { |result| comment(result.output) }
|
|
29
|
+
# book.command('show running-config')
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
class Base
|
|
33
|
+
# Shorthand for {Dialect::Actions} inside dialect definitions.
|
|
34
|
+
Actions = Glazed::Dialect::Actions
|
|
35
|
+
|
|
36
|
+
# Comment prefix used when a dialect declares none.
|
|
37
|
+
DEFAULT_COMMENT_PREFIX = '# ' #: String
|
|
38
|
+
|
|
39
|
+
@parent = nil #: singleton(Base)?
|
|
40
|
+
@prompt = nil #: Regexp?
|
|
41
|
+
@comment_prefix = nil #: String?
|
|
42
|
+
@data_filters = nil #: Array[Dialect::data_filter]?
|
|
43
|
+
@output_filters = nil #: Array[Dialect::data_filter]?
|
|
44
|
+
@interactions = nil #: Array[Dialect::Interaction]?
|
|
45
|
+
@sensitive_patterns = nil #: Array[Dialect::SensitivePattern]?
|
|
46
|
+
@communications = nil #: Array[[Array[Symbol], ^(Dialect::CommunicationConfig) -> void]]?
|
|
47
|
+
@runbooks = nil #: Hash[Symbol, Dialect::Runbook]?
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Records the parent so subclasses inherit declarations.
|
|
52
|
+
#
|
|
53
|
+
# @param subclass [Class<Base>] the new dialect
|
|
54
|
+
# @return [void]
|
|
55
|
+
#: (singleton(Base) subclass) -> void
|
|
56
|
+
def inherited(subclass)
|
|
57
|
+
super
|
|
58
|
+
subclass.parent = self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Declares the command prompt pattern.
|
|
62
|
+
#
|
|
63
|
+
# @param pattern [Regexp] prompt pattern, matched against the session buffer
|
|
64
|
+
# @return [void]
|
|
65
|
+
# @example
|
|
66
|
+
# prompt(/^\S+[#>]\s?$/)
|
|
67
|
+
#: (Regexp pattern) -> void
|
|
68
|
+
def prompt(pattern)
|
|
69
|
+
@prompt = pattern
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Regexp] the declared or inherited prompt pattern
|
|
73
|
+
# @raise [NotImplementedError] when no prompt is declared
|
|
74
|
+
#: () -> Regexp
|
|
75
|
+
def prompt_pattern
|
|
76
|
+
@prompt || @parent&.prompt_pattern || raise(NotImplementedError, "#{name} declares no prompt")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Declares the prefix of comment lines in collected configuration.
|
|
80
|
+
#
|
|
81
|
+
# @param prefix [String] such as "! " or "# "
|
|
82
|
+
# @return [void]
|
|
83
|
+
# @example
|
|
84
|
+
# comment_prefix '! '
|
|
85
|
+
#: (String prefix) -> void
|
|
86
|
+
def comment_prefix(prefix)
|
|
87
|
+
@comment_prefix = prefix
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [String] the declared or inherited comment prefix
|
|
91
|
+
#: () -> String
|
|
92
|
+
def comment_prefix_value
|
|
93
|
+
@comment_prefix || @parent&.comment_prefix_value || DEFAULT_COMMENT_PREFIX
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Turns text into comment lines.
|
|
97
|
+
#
|
|
98
|
+
# @param text [String] text to comment out
|
|
99
|
+
# @return [String] every line prefixed, ending with a newline
|
|
100
|
+
#: (String text) -> String
|
|
101
|
+
def comment(text)
|
|
102
|
+
prefix = comment_prefix_value
|
|
103
|
+
text.each_line.map { |line| "#{prefix}#{line.chomp}".rstrip + "\n" }.join
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Declares a filter for each fragment of received data, such as one
|
|
107
|
+
# removing ANSI sequences. Fragments split at arbitrary points.
|
|
108
|
+
#
|
|
109
|
+
# @yieldparam fragment [String] received data
|
|
110
|
+
# @yieldreturn [String] filtered data
|
|
111
|
+
# @return [void]
|
|
112
|
+
# @example
|
|
113
|
+
# filter_data { |fragment| fragment.delete("\r") }
|
|
114
|
+
#: () { (String fragment) -> String } -> void
|
|
115
|
+
def filter_data(&filter)
|
|
116
|
+
(@data_filters ||= []) << filter
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @return [Array<Proc>] inherited then own data filters
|
|
120
|
+
#: () -> Array[Dialect::data_filter]
|
|
121
|
+
def data_filters = (@parent&.data_filters || []) + (@data_filters || [])
|
|
122
|
+
|
|
123
|
+
# Declares a filter for the whole output of each runbook command. The
|
|
124
|
+
# output arrives as bytes and must be valid UTF-8 once every filter ran,
|
|
125
|
+
# so a dialect for a device using another encoding converts it here.
|
|
126
|
+
#
|
|
127
|
+
# @yieldparam output [String] command output, as bytes (ASCII-8BIT)
|
|
128
|
+
# @yieldreturn [String] filtered output
|
|
129
|
+
# @return [void]
|
|
130
|
+
# @example
|
|
131
|
+
# filter_output { |output| output.gsub(/ +$/, '') }
|
|
132
|
+
#: () { (String output) -> String } -> void
|
|
133
|
+
def filter_output(&filter)
|
|
134
|
+
(@output_filters ||= []) << filter
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @return [Array<Proc>] inherited then own output filters
|
|
138
|
+
#: () -> Array[Dialect::data_filter]
|
|
139
|
+
def output_filters = (@parent&.output_filters || []) + (@output_filters || [])
|
|
140
|
+
|
|
141
|
+
# Declares a response to a pattern appearing in the session buffer.
|
|
142
|
+
# The responder must remove the pattern from the buffer, or it fires
|
|
143
|
+
# again on the next received data.
|
|
144
|
+
#
|
|
145
|
+
# @param pattern [Regexp] pattern to watch for
|
|
146
|
+
# @yieldparam session [CliSession] the session
|
|
147
|
+
# @yieldparam buffer [String] copy of the buffer
|
|
148
|
+
# @yieldparam match [MatchData] the match in the buffer
|
|
149
|
+
# @yieldreturn [Actions::Append, Actions::Replace, Actions::Multi, String, nil] action applied to the buffer
|
|
150
|
+
# @return [void]
|
|
151
|
+
# @example
|
|
152
|
+
# interact(/--More--/) do |cli, _buffer, match|
|
|
153
|
+
# cli.write(' ')
|
|
154
|
+
# Actions::Replace.new(content: match.pre_match + match.post_match)
|
|
155
|
+
# end
|
|
156
|
+
#: (Regexp pattern) { (CliSession session, String buffer, MatchData match) -> Dialect::Actions::action? } -> void
|
|
157
|
+
def interact(pattern, &responder)
|
|
158
|
+
(@interactions ||= []) << Dialect::Interaction.new(pattern:, responder:)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @return [Array<Dialect::Interaction>] inherited then own interactions
|
|
162
|
+
#: () -> Array[Dialect::Interaction]
|
|
163
|
+
def interactions = (@parent&.interactions || []) + (@interactions || [])
|
|
164
|
+
|
|
165
|
+
# Declares where secrets appear in collected configuration.
|
|
166
|
+
#
|
|
167
|
+
# @param pattern [Regexp] pattern matched against the whole configuration
|
|
168
|
+
# @yieldparam match [MatchData] a match
|
|
169
|
+
# @yieldreturn [String, Range<Integer>, nil] the secret within the match, or its offsets in the configuration
|
|
170
|
+
# @return [void]
|
|
171
|
+
# @example
|
|
172
|
+
# sensitive_pattern(/^enable secret \d (\S+)/) { it[1] }
|
|
173
|
+
#: (Regexp pattern) { (MatchData match) -> (String | Range[Integer])? } -> void
|
|
174
|
+
def sensitive_pattern(pattern, &determiner)
|
|
175
|
+
(@sensitive_patterns ||= []) << Dialect::SensitivePattern.new(pattern:, determiner:)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @return [Array<Dialect::SensitivePattern>] inherited then own sensitive patterns
|
|
179
|
+
#: () -> Array[Dialect::SensitivePattern]
|
|
180
|
+
def sensitive_patterns = (@parent&.sensitive_patterns || []) + (@sensitive_patterns || [])
|
|
181
|
+
|
|
182
|
+
# Declares communication settings for transports.
|
|
183
|
+
#
|
|
184
|
+
# @param kinds [Array<Symbol>] transport names, such as :ssh and :telnet
|
|
185
|
+
# @yieldparam config [Dialect::CommunicationConfig] config to adjust
|
|
186
|
+
# @return [void]
|
|
187
|
+
# @example
|
|
188
|
+
# communication :ssh, :telnet do |config|
|
|
189
|
+
# config.post_login { |run| run.cli.command('terminal length 0') }
|
|
190
|
+
# end
|
|
191
|
+
#: (*Symbol kinds) { (Dialect::CommunicationConfig config) -> void } -> void
|
|
192
|
+
def communication(*kinds, &configure)
|
|
193
|
+
(@communications ||= []) << [kinds, configure]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Builds the communication settings for a transport.
|
|
197
|
+
#
|
|
198
|
+
# @param kind [Symbol] transport name
|
|
199
|
+
# @return [Dialect::CommunicationConfig] config with inherited then own blocks applied
|
|
200
|
+
#: (Symbol kind) -> Dialect::CommunicationConfig
|
|
201
|
+
def communication_config(kind)
|
|
202
|
+
config = @parent&.communication_config(kind) || Dialect::CommunicationConfig.new
|
|
203
|
+
(@communications || []).each do |kinds, configure|
|
|
204
|
+
configure.call(config) if kinds.include?(kind)
|
|
205
|
+
end
|
|
206
|
+
config
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Declares the runbook for an operation, replacing any inherited one.
|
|
210
|
+
#
|
|
211
|
+
# @param operation [Symbol] operation name; :get collects the configuration
|
|
212
|
+
# @yieldparam book [Dialect::Runbook::Builder] builder to add steps to
|
|
213
|
+
# @return [void]
|
|
214
|
+
# @example
|
|
215
|
+
# runbook :get do |book|
|
|
216
|
+
# book.command('show running-config')
|
|
217
|
+
# end
|
|
218
|
+
#: (Symbol operation) { (Dialect::Runbook::Builder book) -> void } -> void
|
|
219
|
+
def runbook(operation, &definition)
|
|
220
|
+
builder = Dialect::Runbook::Builder.new
|
|
221
|
+
definition.call(builder)
|
|
222
|
+
(@runbooks ||= {})[operation] = builder.build
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @param operation [Symbol] operation name
|
|
226
|
+
# @return [Dialect::Runbook] the declared or inherited runbook
|
|
227
|
+
# @raise [NotImplementedError] when no runbook is declared for the operation
|
|
228
|
+
#: (Symbol operation) -> Dialect::Runbook
|
|
229
|
+
def runbook_for(operation)
|
|
230
|
+
@runbooks&.[](operation) || @parent&.runbook_for(operation) ||
|
|
231
|
+
raise(NotImplementedError, "#{name} declares no #{operation} runbook")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @param parent [Class<Base>, nil] dialect to inherit declarations from
|
|
235
|
+
# @return [void]
|
|
236
|
+
#: (singleton(Base)? parent) -> void
|
|
237
|
+
protected def parent=(parent)
|
|
238
|
+
@parent = parent
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# A PTY ends lines with CRLF; normalised on whole output, as a fragment may split the pair.
|
|
243
|
+
filter_output { it.gsub(/\r+\n/, "\n") }
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
# Derived from Oxidized (https://github.com/ytti/oxidized) lib/oxidized/model/ios.rb,
|
|
7
|
+
# Copyright 2013-2015 Saku Ytti, Samer Abdel-Hafez and Oxidized contributors,
|
|
8
|
+
# licensed under the Apache License, Version 2.0. Modified for the glazed dialect API.
|
|
9
|
+
|
|
10
|
+
require 'glazed/dialects/base'
|
|
11
|
+
|
|
12
|
+
module Glazed
|
|
13
|
+
module Dialects
|
|
14
|
+
# Cisco IOS and IOS XE. Ported from oxidized's ios model.
|
|
15
|
+
#
|
|
16
|
+
# Node params: "ios_rbac" set to "true" collects `show running-config view full`.
|
|
17
|
+
# Node secrets: "enable" is sent as the enable password; without it, param
|
|
18
|
+
# "enable" set to "true" runs `enable` without one.
|
|
19
|
+
class Ios < Base
|
|
20
|
+
prompt(/^([\w.@()-]+[#>]\s?)$/)
|
|
21
|
+
comment_prefix '! '
|
|
22
|
+
|
|
23
|
+
# Commands some platforms lack leave an error behind.
|
|
24
|
+
filter_output { it.gsub(/^% Invalid input detected at '\^' marker\.$|^\s+\^$/, '') }
|
|
25
|
+
|
|
26
|
+
sensitive_pattern(/^snmp-server community (\S+)/) { it[1] }
|
|
27
|
+
# SNMPv3 hosts carry a user name, not a community.
|
|
28
|
+
sensitive_pattern(/^snmp-server host (?!.* version 3 )\S+(?> vrf \S+)?(?> informs?| traps)?(?> version (?:1|2c))? +(\S+)/) { it[1] }
|
|
29
|
+
sensitive_pattern(/^username .+ (?:password|secret) \d (.+)$/) { it[1] }
|
|
30
|
+
sensitive_pattern(/^enable (?:password|secret)(?: level \d+)? \d (.+)$/) { it[1] }
|
|
31
|
+
sensitive_pattern(/^ +(?:password|secret) (?:\d )?(\S+)/) { it[1] }
|
|
32
|
+
sensitive_pattern(/^.*wpa-psk ascii \d (\S+)/) { it[1] }
|
|
33
|
+
sensitive_pattern(/^.*key 7 (\d.+)$/) { it[1] }
|
|
34
|
+
sensitive_pattern(/^tacacs-server (?:.+ )?key (?:\d )?(.+)$/) { it[1] }
|
|
35
|
+
sensitive_pattern(/^crypto isakmp key (\S+) /) { it[1] }
|
|
36
|
+
sensitive_pattern(/^ +ip ospf message-digest-key \d+ md5 (?:\d )?(.+)$/) { it[1] }
|
|
37
|
+
sensitive_pattern(/^ +ip ospf authentication-key (?:\d )?(.+)$/) { it[1] }
|
|
38
|
+
sensitive_pattern(/^ +neighbor \S+ password (?:\d )?(.+)$/) { it[1] }
|
|
39
|
+
sensitive_pattern(/^ +vrrp \d+ authentication text (.+)$/) { it[1] }
|
|
40
|
+
sensitive_pattern(/^ +standby \d+ authentication (?!md5 )(.{1,8})$/) { it[1] }
|
|
41
|
+
sensitive_pattern(/^ +standby \d+ authentication md5 key-string (?:\d )?(.+?)(?: timeout \d+)?$/) { it[1] }
|
|
42
|
+
sensitive_pattern(/^ +key-string (?:\d )?(.+)$/) { it[1] }
|
|
43
|
+
sensitive_pattern(/^(?:tacacs|radius) server [^\n]+\n(?: +[^\n]+\n)*? +key (?:\d+ )?(\S+)/) { it[1] }
|
|
44
|
+
sensitive_pattern(/^ +ppp (?:chap|pap) password \d (.+)$/) { it[1] }
|
|
45
|
+
sensitive_pattern(/^ +security wpa psk set-key (?:ascii|hex) \d (.+)$/) { it[1] }
|
|
46
|
+
sensitive_pattern(/^ +dot1x username \S+ password \d (.+)$/) { it[1] }
|
|
47
|
+
sensitive_pattern(/^ +mgmtuser username \S+ password \d (.*) secret \d /) { it[1] }
|
|
48
|
+
sensitive_pattern(/^ +mgmtuser username \S+ password \d .* secret \d (.*)$/) { it[1] }
|
|
49
|
+
sensitive_pattern(/^ +client \S+ server-key \d (.+)$/) { it[1] }
|
|
50
|
+
sensitive_pattern(/^ +domain-password (\S+)/) { it[1] }
|
|
51
|
+
sensitive_pattern(/^ +pre-shared-key (.+)$/) { it[1] }
|
|
52
|
+
|
|
53
|
+
runbook :get do |book|
|
|
54
|
+
book.command('show version') { comment(version_summary(it.output)) }
|
|
55
|
+
book.command('show vtp status') do |result|
|
|
56
|
+
vtp = result.output.gsub(/^\s*\n/, '').gsub(/Configuration last modified by.*\n/, '')
|
|
57
|
+
comment("#{vtp.gsub(/^/, 'VTP: ')}\n")
|
|
58
|
+
end
|
|
59
|
+
book.command('show inventory') { comment(it.output) }
|
|
60
|
+
book.raw('show running-config') do |run|
|
|
61
|
+
line = run.param('ios_rbac') == 'true' ? 'show running-config view full' : 'show running-config'
|
|
62
|
+
run.emit(running_config(run.command(line).output))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
communication :telnet do |config|
|
|
67
|
+
config.username_prompt = /^Username:/i
|
|
68
|
+
config.password_prompt = /^Password:/i
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
communication :ssh, :telnet do |config|
|
|
72
|
+
config.post_login do |run|
|
|
73
|
+
if (enable = run.secret('enable'))
|
|
74
|
+
run.cli.write("enable\n")
|
|
75
|
+
run.cli.expect(/^[pP]assword:/)
|
|
76
|
+
run.cli.write("#{enable.value}\n")
|
|
77
|
+
run.cli.expect(run.cli.prompt)
|
|
78
|
+
elsif run.param('enable') == 'true'
|
|
79
|
+
run.cli.command('enable')
|
|
80
|
+
end
|
|
81
|
+
run.cli.command('terminal length 0')
|
|
82
|
+
run.cli.command('terminal width 0')
|
|
83
|
+
end
|
|
84
|
+
config.pre_logout { |run| run.cli.write("exit\n") }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class << self
|
|
88
|
+
# Summarises `show version` into stable facts, as oxidized does.
|
|
89
|
+
#
|
|
90
|
+
# @param output [String] output of `show version`
|
|
91
|
+
# @return [String] summary lines, ending with a blank line
|
|
92
|
+
#: (String output) -> String
|
|
93
|
+
def version_summary(output)
|
|
94
|
+
lines = output.lines
|
|
95
|
+
facts = lines.first(1)
|
|
96
|
+
lines.each_with_index do |line, index|
|
|
97
|
+
facts.concat(version_facts(line, lines[index + 1], lines[index + 2]))
|
|
98
|
+
end
|
|
99
|
+
facts << "\n"
|
|
100
|
+
facts.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Cleans `show running-config` of lines that change without configuration changes.
|
|
104
|
+
#
|
|
105
|
+
# @param output [String] output of `show running-config`
|
|
106
|
+
# @return [String] the configuration
|
|
107
|
+
#: (String output) -> String
|
|
108
|
+
def running_config(output)
|
|
109
|
+
config = output
|
|
110
|
+
.sub(/\A\s*Building configuration\.\.\.\n\s*\n?/, '')
|
|
111
|
+
.sub(/^Current configuration : [^\n]*\n/, '')
|
|
112
|
+
config = config.each_line.reject do |line|
|
|
113
|
+
line.start_with?('ntp clock-period ') ||
|
|
114
|
+
(line.match?(/^! (Last|No) configuration change (at|since).*/) && !line.match?(/\d+\sby\s\S+$/))
|
|
115
|
+
end.join
|
|
116
|
+
config
|
|
117
|
+
.gsub(/^ tunnel mpls traffic-eng bandwidth[^\n]*\n*((?: [^\n]*\n*)*tunnel mpls traffic-eng auto-bw)/m, '\1')
|
|
118
|
+
.gsub(/^(\s+expression) \d+$/, '\1 <value removed>')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @param line [String] a line of `show version`
|
|
122
|
+
# @param next_line [String, nil] the line after it
|
|
123
|
+
# @param line_after [String, nil] the line after that
|
|
124
|
+
# @return [Array<String>] facts found on the line
|
|
125
|
+
#: (String line, String? next_line, String? line_after) -> Array[String]
|
|
126
|
+
private def version_facts(line, next_line, line_after)
|
|
127
|
+
slave = (slot = line[/^Slave in slot (\d+) is running/, 1]) ? ' Slave:' : ''
|
|
128
|
+
slave_slot = slot ? ", slot #{slot}" : ''
|
|
129
|
+
facts = [] #: Array[String]
|
|
130
|
+
if (m = line.match(/^Compiled (.*)$/))
|
|
131
|
+
facts << "Image:#{slave} Compiled: #{m[1]}"
|
|
132
|
+
end
|
|
133
|
+
if (m = line.match(/^(?:Cisco )?IOS .* Software,? \(([A-Za-z0-9_-]*)\), .*Version\s+(.*)$/))
|
|
134
|
+
facts << "Image:#{slave} Software: #{m[1]}, #{m[2]}"
|
|
135
|
+
end
|
|
136
|
+
if (m = line.match(/^ROM: (IOS \S+ )?(System )?Bootstrap.*(Version.*)$/))
|
|
137
|
+
facts << "ROM Bootstrap: #{m[3]}"
|
|
138
|
+
end
|
|
139
|
+
if (m = line.match(/^BOOTFLASH: .*(Version.*)$/))
|
|
140
|
+
facts << "BOOTFLASH: #{m[1]}"
|
|
141
|
+
end
|
|
142
|
+
if (m = line.match(/^(\d+[kK]) bytes of (non-volatile|NVRAM)/))
|
|
143
|
+
facts << "Memory: nvram #{m[1]}"
|
|
144
|
+
end
|
|
145
|
+
if (m = line.match(/^(\d+[kK]) bytes of (flash memory|flash internal|processor board System flash|ATA CompactFlash)/i))
|
|
146
|
+
facts << "Memory: flash #{m[1]}"
|
|
147
|
+
end
|
|
148
|
+
if (m = line.match(/^(\d+[kK]) bytes of (Flash|ATA)?.*PCMCIA .*(slot|disk) ?(\d)/i))
|
|
149
|
+
facts << "Memory: pcmcia #{m[2]} #{m[3]}#{m[4]} #{m[1]}"
|
|
150
|
+
end
|
|
151
|
+
if (m = line.match(/(\S+(?:\sseries)?)\s+(?:\(([\S ]+)\)\s+processor|\(revision[^)]+\)).*\s+with (\S+k) bytes/i))
|
|
152
|
+
cpu = m[2]
|
|
153
|
+
facts << "Chassis type:#{slave} #{m[1]}"
|
|
154
|
+
facts << "Memory:#{slave} main #{m[3]}"
|
|
155
|
+
if (id = next_line&.match(/processor board id (\S+)/i))
|
|
156
|
+
facts << "Processor ID: #{id[1]}"
|
|
157
|
+
end
|
|
158
|
+
cpu_extra = if line_after&.match?(/(cpu at |processor: |#{cpu} processor,)/i)
|
|
159
|
+
line_after.gsub('implementation', 'impl').gsub(/^/, ', ').chomp
|
|
160
|
+
else
|
|
161
|
+
''
|
|
162
|
+
end
|
|
163
|
+
facts << "CPU:#{slave} #{cpu}#{cpu_extra}#{slave_slot}"
|
|
164
|
+
end
|
|
165
|
+
if (m = line.match(/^System image file is "([^"]*)"$/))
|
|
166
|
+
facts << "Image: #{m[1]}"
|
|
167
|
+
end
|
|
168
|
+
facts
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
# Derived from Oxidized (https://github.com/ytti/oxidized) lib/oxidized/model/junos.rb,
|
|
7
|
+
# Copyright 2013-2015 Saku Ytti, Samer Abdel-Hafez and Oxidized contributors,
|
|
8
|
+
# licensed under the Apache License, Version 2.0. Modified for the glazed dialect API.
|
|
9
|
+
|
|
10
|
+
require 'glazed/dialects/base'
|
|
11
|
+
|
|
12
|
+
module Glazed
|
|
13
|
+
module Dialects
|
|
14
|
+
# Juniper Junos OS.
|
|
15
|
+
#
|
|
16
|
+
# Node params: "junos_license" set to "false" skips the license commands,
|
|
17
|
+
# whose usage counts change without configuration changes.
|
|
18
|
+
class Junos < Base
|
|
19
|
+
prompt(/^(\r?[\w.@:\/-]+[#>]\s?)$/)
|
|
20
|
+
comment_prefix '# '
|
|
21
|
+
|
|
22
|
+
filter_output { |output| output.gsub(/[ \t\r]+$/, '') }
|
|
23
|
+
# Counters that change without configuration changes.
|
|
24
|
+
filter_output do |output|
|
|
25
|
+
output
|
|
26
|
+
.gsub(/ scale-subscriber (\s+)(\d+)/, ' scale-subscriber <count>')
|
|
27
|
+
.gsub(/VMX-BANDWIDTH\s+(\d+) (.*)/, 'VMX-BANDWIDTH <count> \2')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sensitive_pattern(/ community (.+) \{$/) { it[1] }
|
|
31
|
+
sensitive_pattern(/"(.+)".*; ## SECRET-DATA$/) { it[1] }
|
|
32
|
+
|
|
33
|
+
runbook :get do |book|
|
|
34
|
+
book.raw('show version') do |run|
|
|
35
|
+
output = run.command('show version').output
|
|
36
|
+
run.vars[:model] = output[/^Model: (\S+)/, 1]
|
|
37
|
+
run.emit(comment(output))
|
|
38
|
+
end
|
|
39
|
+
book.command('show chassis hardware') { comment(it.output) }
|
|
40
|
+
book.raw('licenses') do |run|
|
|
41
|
+
next if run.param('junos_license') == 'false'
|
|
42
|
+
|
|
43
|
+
run.emit(
|
|
44
|
+
comment(
|
|
45
|
+
run.command('show system license').output
|
|
46
|
+
.gsub(/ fib-scale\s+(\d+)/, ' fib-scale <count>')
|
|
47
|
+
.gsub(/ rib-scale\s+(\d+)/, ' rib-scale <count>'),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
run.emit(comment(run.command('show system license keys').output))
|
|
51
|
+
end
|
|
52
|
+
book.command('show configuration | display omit')
|
|
53
|
+
book.raw('model-specific status') do |run|
|
|
54
|
+
case run.vars[:model]
|
|
55
|
+
when 'mx960'
|
|
56
|
+
run.emit(comment(run.command('show chassis fabric reachability').output))
|
|
57
|
+
when /^(ex22|ex3[34]|ex4|ex8|qfx)/
|
|
58
|
+
run.emit(comment(run.command('show virtual-chassis').output))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
communication :telnet do |config|
|
|
64
|
+
config.username_prompt = /^login:/i
|
|
65
|
+
config.password_prompt = /^password:/i
|
|
66
|
+
config.post_login do |run|
|
|
67
|
+
run.cli.command('set cli screen-length 0')
|
|
68
|
+
run.cli.command('set cli screen-width 0')
|
|
69
|
+
end
|
|
70
|
+
config.pre_logout { |run| run.cli.write("exit\n") }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
communication :ssh do |config|
|
|
74
|
+
config.exec = true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
# Derived from Oxidized (https://github.com/ytti/oxidized) lib/oxidized/model/necix.rb,
|
|
7
|
+
# Copyright 2013-2015 Saku Ytti, Samer Abdel-Hafez and Oxidized contributors,
|
|
8
|
+
# licensed under the Apache License, Version 2.0. Modified for the glazed dialect API.
|
|
9
|
+
|
|
10
|
+
require 'glazed/dialects/base'
|
|
11
|
+
|
|
12
|
+
module Glazed
|
|
13
|
+
module Dialects
|
|
14
|
+
# NEC UNIVERGE IX series.
|
|
15
|
+
class NecIx < Base
|
|
16
|
+
prompt(/^(\([\w.-]*\)\s[#$]|^\S+[$#]\s?)$/)
|
|
17
|
+
comment_prefix '! '
|
|
18
|
+
|
|
19
|
+
interact(/^--More--$/) do |cli, _buffer, match|
|
|
20
|
+
cli.write(' ')
|
|
21
|
+
Actions::Replace.new(content: match.pre_match + match.post_match)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sensitive_pattern(/^username \S+ password \S+ (\S+)/) { it[1] }
|
|
25
|
+
sensitive_pattern(/^snmp-agent \S+ community (\S+).*/) { it[1] }
|
|
26
|
+
sensitive_pattern(/^ikev2 authentication psk id .+ key \S+ (\S+).*/) { it[1] }
|
|
27
|
+
sensitive_pattern(/^ike policy ike-policy .+ key \S+ (\S+).*/) { it[1] }
|
|
28
|
+
sensitive_pattern(/^radius host .+ key \S+ (\S+).*/) { it[1] }
|
|
29
|
+
sensitive_pattern(/^\s*authentication (?:secret-)?password \S+ (\S+)/) { it[1] }
|
|
30
|
+
sensitive_pattern(/^\s*ngn radius-auth password \S+ (\S+)/) { it[1] }
|
|
31
|
+
sensitive_pattern(/^\s*usbmem authentication .+ password-file \S+ \S+ (\S+)/) { it[1] }
|
|
32
|
+
sensitive_pattern(/^web-auth username \S+ (?:secret-)?password (\S+)/) { it[1] }
|
|
33
|
+
sensitive_pattern(/^\s*area \S+ virtual-link .* authentication-key (\S+)/) { it[1] }
|
|
34
|
+
sensitive_pattern(/^\s*ip ospf authentication-key (\S+)/) { it[1] }
|
|
35
|
+
sensitive_pattern(/^\s*ip ospf message-digest-key (\S+)/) { it[1] }
|
|
36
|
+
sensitive_pattern(/^\s*neighbor \S+ password (\S+)/) { it[1] }
|
|
37
|
+
sensitive_pattern(/^\s*password (?:plain |secret )?(\S+)/) { it[1] }
|
|
38
|
+
sensitive_pattern(/^\s*l2tp password \S+ (\S+)/) { it[1] }
|
|
39
|
+
sensitive_pattern(/^\s*vrrp \S+ authentication (\S+)/) { it[1] }
|
|
40
|
+
sensitive_pattern(/^\s*nm account \S+ password \S+ (\S+)/) { it[1] }
|
|
41
|
+
sensitive_pattern(/^snmpv3 user \S+ .+ priv \S+ (?:plain|secret) (\S+)/) { it[1] }
|
|
42
|
+
sensitive_pattern(/^snmpv3 user \S+ .+? (?:plain|secret) (\S+)/) { it[1] }
|
|
43
|
+
sensitive_pattern(/^http-server cross-site (?:secret-)?password (\S+)/) { it[1] }
|
|
44
|
+
sensitive_pattern(/^http-server (?:guest-|monitor-)?username \S+ (?:secret-)?password (\S+)/) { it[1] }
|
|
45
|
+
sensitive_pattern(/^startup (?:config-download|software-update) .+ (?:secret-)?password (\S+)/) { it[1] }
|
|
46
|
+
|
|
47
|
+
runbook :get do |book|
|
|
48
|
+
book.command('show running-config') do |result|
|
|
49
|
+
result.output.each_line.grep_v(/^!.*Current time.*$/).join
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
communication :telnet do |config|
|
|
54
|
+
config.username_prompt = /^Username:\s/i
|
|
55
|
+
config.password_prompt = /^Password:\s/i
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
communication :ssh, :telnet do |config|
|
|
59
|
+
config.post_login do |run|
|
|
60
|
+
raise Glazed::Error, 'CONFIG process is occupied' if run.cli.command('configure').output.match?(/CONFIG process is occupied/)
|
|
61
|
+
|
|
62
|
+
run.cli.command('terminal length 0')
|
|
63
|
+
end
|
|
64
|
+
config.pre_logout do |run|
|
|
65
|
+
run.cli.write("\cZ")
|
|
66
|
+
run.cli.write("exit\n")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
# Derived from Oxidized (https://github.com/ytti/oxidized) lib/oxidized/model/aireos.rb,
|
|
7
|
+
# Copyright 2013-2015 Saku Ytti, Samer Abdel-Hafez and Oxidized contributors,
|
|
8
|
+
# licensed under the Apache License, Version 2.0. Modified for the glazed dialect API.
|
|
9
|
+
|
|
10
|
+
require 'glazed/dialects/base'
|
|
11
|
+
|
|
12
|
+
module Glazed
|
|
13
|
+
module Dialects
|
|
14
|
+
# Cisco Wireless LAN Controller on AireOS. Ported from oxidized's aireos model.
|
|
15
|
+
#
|
|
16
|
+
# AireOS asks for the user name and password inside the session, after SSH
|
|
17
|
+
# authentication as well.
|
|
18
|
+
class Wlc < Base
|
|
19
|
+
prompt(/^\([^)]+\)\s>/)
|
|
20
|
+
comment_prefix '# '
|
|
21
|
+
|
|
22
|
+
sensitive_pattern(/^config snmp community create (\S+)/) { it[1] }
|
|
23
|
+
sensitive_pattern(/^config wlan security wpa akm psk set-key (?:ascii|hex) (\S+)/) { it[1] }
|
|
24
|
+
sensitive_pattern(/^config (?:radius|tacacs) (?:auth|acct|athr) add \d+ \S+ \d+ (?:ascii|hex) (\S+)/) { it[1] }
|
|
25
|
+
|
|
26
|
+
runbook :get do |book|
|
|
27
|
+
book.command('show udi') { comment("#{clean(it.output)}\n") }
|
|
28
|
+
book.command('show boot') { comment("#{clean(it.output)}\n") }
|
|
29
|
+
book.command('show run-config commands') { clean(it.output) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
communication :ssh, :telnet do |config|
|
|
33
|
+
config.username_prompt = /^User:\s*/
|
|
34
|
+
config.password_prompt = /^Password:\s*/
|
|
35
|
+
config.post_login { |run| run.cli.command('config paging disable') }
|
|
36
|
+
# Logout asks whether to save unsaved changes.
|
|
37
|
+
config.pre_logout do |run|
|
|
38
|
+
run.cli.write("logout\n")
|
|
39
|
+
run.cli.write('n')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Drops blank lines, rogue alerts and surrounding spaces.
|
|
44
|
+
#
|
|
45
|
+
# @param output [String] command output
|
|
46
|
+
# @return [String] cleaned lines, ending with a newline
|
|
47
|
+
#: (String output) -> String
|
|
48
|
+
def self.clean(output)
|
|
49
|
+
lines = output.each_line.map(&:strip).reject do |line|
|
|
50
|
+
line.empty? || line.match?(/rogue (adhoc|client) (alert|Unknown) [\da-f]{2}:/)
|
|
51
|
+
end
|
|
52
|
+
"#{lines.join("\n")}\n"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|