oxidized 0.29.1 → 0.37.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 +4 -4
- data/.coderabbit.yaml +21 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +45 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- data/.github/ISSUE_TEMPLATE/support-request.md +39 -0
- data/.github/workflows/codeql.yml +4 -4
- data/.github/workflows/publishdocker.yml +108 -13
- data/.github/workflows/ruby.yml +3 -12
- data/.github/workflows/stale.yml +6 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +69 -13
- data/.rubocop_todo.yml +13 -143
- data/CHANGELOG.md +431 -4
- data/CONTRIBUTING.md +189 -0
- data/Dockerfile +89 -34
- data/README.md +59 -188
- data/Rakefile +58 -12
- data/bin/oxidized +2 -5
- data/docs/Configuration.md +382 -149
- data/docs/Creating-Models.md +262 -10
- data/docs/DeviceSimulation.md +196 -0
- data/docs/Docker.md +246 -0
- data/docs/Hooks.md +146 -51
- data/docs/Inputs.md +237 -0
- data/docs/Issues.md +123 -0
- data/docs/Model-Notes/APC.md +72 -0
- data/docs/Model-Notes/Cumulus.md +5 -0
- data/docs/Model-Notes/EatonNetwork.md +18 -0
- data/docs/Model-Notes/ExaLink.md +43 -0
- data/docs/Model-Notes/FSOS.md +17 -0
- data/docs/Model-Notes/Fortinet.md +75 -0
- data/docs/Model-Notes/GrandstreamHT8xx.md +8 -0
- data/docs/Model-Notes/HPEAruba.md +32 -0
- data/docs/Model-Notes/IvantiConnectSecure.md +59 -0
- data/docs/Model-Notes/OS6.md +10 -0
- data/docs/Model-Notes/README.md +1 -20
- data/docs/Model-Notes/RouterOS.md +28 -0
- data/docs/Model-Notes/SikluMHTG.md +7 -0
- data/docs/Model-Notes/TrueNAS.md +23 -0
- data/docs/Model-Notes/XGS4600-Zyxel.md +5 -0
- data/docs/ModelUnitTests.md +219 -0
- data/docs/Outputs.md +102 -5
- data/docs/Release.md +102 -0
- data/docs/Ruby-API.md +135 -2
- data/docs/Sources.md +34 -0
- data/docs/Supported-OS-Types.md +207 -270
- data/docs/Troubleshooting.md +43 -1
- data/extra/device2yaml.rb +259 -0
- data/extra/gitdiff-msteams.sh +32 -5
- data/extra/hooks/modelrules.rb +55 -0
- data/extra/hooks/modelrulesadvanced.rb +167 -0
- data/extra/hooks/srcipmap.rb +54 -0
- data/extra/nagios_check_failing_nodes.rb +1 -1
- data/extra/oxidized.runit +1 -1
- data/extra/rest_client.rb +5 -4
- data/extra/syslog.rb +11 -6
- data/lib/oxidized/cli/support.rb +152 -0
- data/lib/oxidized/cli.rb +21 -6
- data/lib/oxidized/config/vars.rb +33 -14
- data/lib/oxidized/config.rb +28 -17
- data/lib/oxidized/core.rb +50 -8
- data/lib/oxidized/hook/ciscosparkdiff.rb +11 -9
- data/lib/oxidized/hook/exec.rb +5 -4
- data/lib/oxidized/hook/githubrepo.rb +55 -18
- data/lib/oxidized/hook/noophook.rb +2 -2
- data/lib/oxidized/hook/slackdiff.rb +50 -14
- data/lib/oxidized/hook/xmppdiff.rb +9 -9
- data/lib/oxidized/hook.rb +68 -16
- data/lib/oxidized/input/cli.rb +8 -3
- data/lib/oxidized/input/debugtext.rb +40 -0
- data/lib/oxidized/input/debugyaml.rb +82 -0
- data/lib/oxidized/input/exec.rb +2 -11
- data/lib/oxidized/input/ftp.rb +2 -19
- data/lib/oxidized/input/http.rb +67 -17
- data/lib/oxidized/input/input.rb +35 -14
- data/lib/oxidized/input/scp.rb +27 -0
- data/lib/oxidized/input/ssh.rb +59 -83
- data/lib/oxidized/input/sshbase.rb +102 -0
- data/lib/oxidized/input/telnet.rb +20 -19
- data/lib/oxidized/input/tftp.rb +8 -8
- data/lib/oxidized/job.rb +18 -7
- data/lib/oxidized/jobs.rb +11 -1
- data/lib/oxidized/logger.rb +51 -0
- data/lib/oxidized/manager.rb +18 -17
- data/lib/oxidized/model/acos.rb +2 -1
- data/lib/oxidized/model/addpack.rb +26 -0
- data/lib/oxidized/model/adtran.rb +5 -1
- data/lib/oxidized/model/adva.rb +3 -3
- data/lib/oxidized/model/airfiber.rb +1 -1
- data/lib/oxidized/model/aos7.rb +11 -0
- data/lib/oxidized/model/aoscx.rb +37 -29
- data/lib/oxidized/model/aosw.rb +39 -29
- data/lib/oxidized/model/apc_aos.rb +7 -2
- data/lib/oxidized/model/apcaos.rb +39 -0
- data/lib/oxidized/model/aricentiss.rb +9 -2
- data/lib/oxidized/model/arubainstant.rb +93 -0
- data/lib/oxidized/model/asa.rb +11 -10
- data/lib/oxidized/model/asternos.rb +22 -0
- data/lib/oxidized/model/asyncos.rb +3 -3
- data/lib/oxidized/model/audiocodes.rb +2 -2
- data/lib/oxidized/model/awplus.rb +15 -12
- data/lib/oxidized/model/axos.rb +1 -1
- data/lib/oxidized/model/bdcom.rb +1 -0
- data/lib/oxidized/model/c4cmts.rb +1 -2
- data/lib/oxidized/model/ciscosma.rb +1 -1
- data/lib/oxidized/model/ciscosmb.rb +6 -1
- data/lib/oxidized/model/cnos.rb +13 -10
- data/lib/oxidized/model/comtrol.rb +1 -1
- data/lib/oxidized/model/comware.rb +5 -3
- data/lib/oxidized/model/cumulus.rb +14 -2
- data/lib/oxidized/model/defacto.rb +26 -0
- data/lib/oxidized/model/dellx.rb +1 -1
- data/lib/oxidized/model/dlink.rb +5 -2
- data/lib/oxidized/model/dlinknextgen.rb +55 -0
- data/lib/oxidized/model/dnos.rb +3 -0
- data/lib/oxidized/model/dslcommands.rb +93 -0
- data/lib/oxidized/model/dslsetup.rb +102 -0
- data/lib/oxidized/model/eatonnetwork.rb +70 -0
- data/lib/oxidized/model/edgecos.rb +3 -1
- data/lib/oxidized/model/edgeos.rb +7 -6
- data/lib/oxidized/model/edgeswitch.rb +3 -1
- data/lib/oxidized/model/efos.rb +41 -0
- data/lib/oxidized/model/eltex.rb +4 -2
- data/lib/oxidized/model/enterasys800.rb +2 -2
- data/lib/oxidized/model/enterprise_sonic.rb +46 -0
- data/lib/oxidized/model/eos.rb +3 -1
- data/lib/oxidized/model/exalink.rb +36 -0
- data/lib/oxidized/model/f5os.rb +17 -0
- data/lib/oxidized/model/fabricos.rb +2 -2
- data/lib/oxidized/model/fastiron.rb +5 -3
- data/lib/oxidized/model/firebrick.rb +2 -2
- data/lib/oxidized/model/firelinuxos.rb +13 -6
- data/lib/oxidized/model/firewareos.rb +10 -1
- data/lib/oxidized/model/fortigate.rb +160 -0
- data/lib/oxidized/model/fortios.rb +38 -48
- data/lib/oxidized/model/fsos.rb +46 -0
- data/lib/oxidized/model/gaiaos.rb +4 -4
- data/lib/oxidized/model/garderos.rb +38 -0
- data/lib/oxidized/model/grandstreamht8xx.rb +19 -0
- data/lib/oxidized/model/h3c.rb +1 -1
- data/lib/oxidized/model/ibos.rb +1 -0
- data/lib/oxidized/model/ingate.rb +47 -0
- data/lib/oxidized/model/ios.rb +57 -30
- data/lib/oxidized/model/iosxr.rb +2 -2
- data/lib/oxidized/model/ironware.rb +7 -5
- data/lib/oxidized/model/ivanti.rb +54 -0
- data/lib/oxidized/model/junos.rb +12 -5
- data/lib/oxidized/model/kornfeldos.rb +33 -0
- data/lib/oxidized/model/lenovonos.rb +2 -0
- data/lib/oxidized/model/linksyssrw.rb +3 -3
- data/lib/oxidized/model/linuxgeneric.rb +4 -2
- data/lib/oxidized/model/macros.rb +60 -0
- data/lib/oxidized/model/mimosab11.rb +34 -0
- data/lib/oxidized/model/ml66.rb +33 -0
- data/lib/oxidized/model/mlnxos.rb +18 -14
- data/lib/oxidized/model/model.rb +98 -103
- data/lib/oxidized/model/mtrlrfs.rb +1 -0
- data/lib/oxidized/model/ndms.rb +6 -0
- data/lib/oxidized/model/netgear.rb +14 -3
- data/lib/oxidized/model/netonix.rb +1 -1
- data/lib/oxidized/model/netscaler.rb +1 -1
- data/lib/oxidized/model/nodegrid.rb +1 -1
- data/lib/oxidized/model/nsxdfw.rb +31 -0
- data/lib/oxidized/model/nsxfirewall.rb +2 -1
- data/lib/oxidized/model/nxos.rb +20 -15
- data/lib/oxidized/model/ocnos.rb +42 -0
- data/lib/oxidized/model/onefinity.rb +18 -0
- data/lib/oxidized/model/openbsd.rb +1 -1
- data/lib/oxidized/model/opengear.rb +36 -1
- data/lib/oxidized/model/openwrt.rb +6 -6
- data/lib/oxidized/model/opnsense.rb +9 -1
- data/lib/oxidized/model/os6.rb +48 -0
- data/lib/oxidized/model/outputs.rb +5 -0
- data/lib/oxidized/model/panos.rb +2 -0
- data/lib/oxidized/model/perle.rb +41 -0
- data/lib/oxidized/model/pfsense.rb +9 -0
- data/lib/oxidized/model/powerconnect.rb +32 -11
- data/lib/oxidized/model/procurve.rb +6 -2
- data/lib/oxidized/model/qtech.rb +3 -1
- data/lib/oxidized/model/quantaos.rb +8 -6
- data/lib/oxidized/model/rgos.rb +33 -0
- data/lib/oxidized/model/riverbed.rb +104 -0
- data/lib/oxidized/model/routeros.rb +18 -11
- data/lib/oxidized/model/saos.rb +18 -1
- data/lib/oxidized/model/saos10.rb +38 -0
- data/lib/oxidized/model/siklu.rb +1 -1
- data/lib/oxidized/model/siklumhtg.rb +22 -0
- data/lib/oxidized/model/sixwind.rb +28 -0
- data/lib/oxidized/model/slxos.rb +3 -3
- data/lib/oxidized/model/smartbyte.rb +48 -0
- data/lib/oxidized/model/sonicos.rb +27 -20
- data/lib/oxidized/model/speedtouch.rb +1 -1
- data/lib/oxidized/model/sros.rb +3 -3
- data/lib/oxidized/model/srosmd.rb +1 -1
- data/lib/oxidized/model/supermicro.rb +1 -1
- data/lib/oxidized/model/telco.rb +1 -1
- data/lib/oxidized/model/tmos.rb +1 -0
- data/lib/oxidized/model/tnsr.rb +57 -0
- data/lib/oxidized/model/tplink.rb +5 -5
- data/lib/oxidized/model/trango.rb +4 -2
- data/lib/oxidized/model/truenas.rb +64 -3
- data/lib/oxidized/model/ucs.rb +1 -1
- data/lib/oxidized/model/unifiap.rb +144 -0
- data/lib/oxidized/model/uplinkolt.rb +46 -0
- data/lib/oxidized/model/voltaire.rb +1 -1
- data/lib/oxidized/model/voss.rb +3 -0
- data/lib/oxidized/model/vrp.rb +5 -1
- data/lib/oxidized/model/vyatta.rb +9 -3
- data/lib/oxidized/model/vyos.rb +37 -0
- data/lib/oxidized/model/xos.rb +10 -1
- data/lib/oxidized/model/zhoneolt.rb +4 -2
- data/lib/oxidized/model/zynos.rb +67 -3
- data/lib/oxidized/model/zynosadsl.rb +14 -0
- data/lib/oxidized/model/zynoscli.rb +1 -1
- data/lib/oxidized/model/zynosgs.rb +2 -0
- data/lib/oxidized/model/zynosmgs.rb +32 -0
- data/lib/oxidized/node.rb +99 -64
- data/lib/oxidized/nodes.rb +40 -19
- data/lib/oxidized/output/file.rb +80 -43
- data/lib/oxidized/output/git.rb +295 -151
- data/lib/oxidized/output/gitcrypt.rb +193 -185
- data/lib/oxidized/output/http.rb +63 -51
- data/lib/oxidized/output/output.rb +20 -4
- data/lib/oxidized/signals.rb +44 -0
- data/lib/oxidized/source/csv.rb +49 -49
- data/lib/oxidized/source/http.rb +76 -63
- data/lib/oxidized/source/jsonfile.rb +68 -0
- data/lib/oxidized/source/source.rb +73 -18
- data/lib/oxidized/source/sql.rb +71 -59
- data/lib/oxidized/version.rb +6 -4
- data/lib/oxidized/worker.rb +57 -29
- data/lib/oxidized.rb +4 -24
- data/lib/refinements.rb +21 -1
- data/oxidized.gemspec +45 -21
- metadata +248 -41
- data/lib/oxidized/model/timos.rb +0 -10
|
@@ -1,20 +1,30 @@
|
|
|
1
|
+
require 'rugged'
|
|
2
|
+
|
|
1
3
|
class GithubRepo < Oxidized::Hook
|
|
2
4
|
def validate_cfg!
|
|
3
5
|
raise KeyError, 'hook.remote_repo is required' unless cfg.has_key?('remote_repo')
|
|
4
6
|
end
|
|
5
7
|
|
|
6
8
|
def run_hook(ctx)
|
|
9
|
+
unless ctx.node
|
|
10
|
+
logger.error 'GithubRepo.run_hook: no node provided'
|
|
11
|
+
return
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
unless ctx.node.repo
|
|
15
|
+
logger.error "Oxidized output is not git, can't push to remote"
|
|
16
|
+
return
|
|
17
|
+
end
|
|
7
18
|
repo = Rugged::Repository.new(ctx.node.repo)
|
|
8
19
|
creds = credentials(ctx.node)
|
|
9
20
|
url = remote_repo(ctx.node)
|
|
10
21
|
|
|
11
22
|
if url.nil? || url.empty?
|
|
12
|
-
|
|
23
|
+
logger.error "No repository defined for #{ctx.node.group}/#{ctx.node.name}"
|
|
13
24
|
return
|
|
14
25
|
end
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
log "to remote: #{url}"
|
|
27
|
+
logger.info "Pushing local repository(#{repo.path}) to remote: #{url}"
|
|
18
28
|
|
|
19
29
|
if repo.remotes['origin'].nil?
|
|
20
30
|
repo.remotes.create('origin', url)
|
|
@@ -23,28 +33,46 @@ class GithubRepo < Oxidized::Hook
|
|
|
23
33
|
end
|
|
24
34
|
remote = repo.remotes['origin']
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
begin
|
|
37
|
+
fetch_and_merge_remote(repo, creds)
|
|
38
|
+
remote.push([repo.head.name], credentials: creds)
|
|
39
|
+
rescue Rugged::NetworkError => e
|
|
40
|
+
if e.message == 'unsupported URL protocol'
|
|
41
|
+
logger.warn "Rugged does not support the git URL '#{url}'."
|
|
42
|
+
unless Rugged.features.include?(:ssh)
|
|
43
|
+
logger.warn "Note: Rugged isn't installed with ssh support. You may need " \
|
|
44
|
+
'"gem install rugged -- --with-ssh"'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
# re-raise exception for the calling method
|
|
48
|
+
raise
|
|
49
|
+
end
|
|
29
50
|
end
|
|
30
51
|
|
|
31
52
|
def fetch_and_merge_remote(repo, creds)
|
|
32
53
|
result = repo.fetch('origin', [repo.head.name], credentials: creds)
|
|
33
|
-
|
|
54
|
+
logger.debug result.inspect
|
|
55
|
+
|
|
56
|
+
their_branch = remote_branch(repo)
|
|
34
57
|
|
|
35
|
-
unless
|
|
36
|
-
|
|
58
|
+
unless their_branch
|
|
59
|
+
logger.debug 'remote branch does not exist yet, nothing to merge'
|
|
37
60
|
return
|
|
38
61
|
end
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
result = repo.merge_analysis(their_branch.target_id)
|
|
41
64
|
|
|
42
|
-
|
|
65
|
+
if result.include? :up_to_date
|
|
66
|
+
logger.debug 'nothing to merge'
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
logger.debug "merging fetched branch #{their_branch.name}"
|
|
43
71
|
|
|
44
72
|
merge_index = repo.merge_commits(repo.head.target_id, their_branch.target_id)
|
|
45
73
|
|
|
46
74
|
if merge_index.conflicts?
|
|
47
|
-
|
|
75
|
+
logger.warn "Conflicts detected, skipping Rugged::Commit.create"
|
|
48
76
|
return
|
|
49
77
|
end
|
|
50
78
|
|
|
@@ -58,21 +86,24 @@ class GithubRepo < Oxidized::Hook
|
|
|
58
86
|
private
|
|
59
87
|
|
|
60
88
|
def credentials(node)
|
|
61
|
-
|
|
89
|
+
proc do |_url, username_from_url, _allowed_types|
|
|
62
90
|
git_user = cfg.has_key?('username') ? cfg.username : (username_from_url || 'git')
|
|
63
91
|
if cfg.has_key?('password')
|
|
64
|
-
|
|
92
|
+
logger.debug "Authenticating using username and password as '#{git_user}'"
|
|
65
93
|
Rugged::Credentials::UserPassword.new(username: git_user, password: cfg.password)
|
|
66
94
|
elsif cfg.has_key?('privatekey')
|
|
67
95
|
pubkey = cfg.has_key?('publickey') ? cfg.publickey : nil
|
|
68
|
-
|
|
96
|
+
logger.debug "Authenticating using ssh keys as '#{git_user}'"
|
|
69
97
|
rugged_sshkey(git_user: git_user, privkey: cfg.privatekey, pubkey: pubkey)
|
|
70
|
-
elsif cfg.has_key?('remote_repo') &&
|
|
98
|
+
elsif cfg.has_key?('remote_repo') &&
|
|
99
|
+
cfg.remote_repo.respond_to?(:has_key?) &&
|
|
100
|
+
cfg.remote_repo.has_key?(node.group) &&
|
|
101
|
+
cfg.remote_repo[node.group].has_key?('privatekey')
|
|
71
102
|
pubkey = cfg.remote_repo[node.group].has_key?('publickey') ? cfg.remote_repo[node.group].publickey : nil
|
|
72
|
-
|
|
103
|
+
logger.debug "Authenticating using ssh keys as '#{git_user}' for '#{node.group}/#{node.name}'"
|
|
73
104
|
rugged_sshkey(git_user: git_user, privkey: cfg.remote_repo[node.group].privatekey, pubkey: pubkey)
|
|
74
105
|
else
|
|
75
|
-
|
|
106
|
+
logger.debug "Authenticating using ssh agent as '#{git_user}'"
|
|
76
107
|
Rugged::Credentials::SshKeyFromAgent.new(username: git_user)
|
|
77
108
|
end
|
|
78
109
|
end
|
|
@@ -97,4 +128,10 @@ class GithubRepo < Oxidized::Hook
|
|
|
97
128
|
cfg.remote_repo[node.group].url
|
|
98
129
|
end
|
|
99
130
|
end
|
|
131
|
+
|
|
132
|
+
# Returns a Rugged::Branch to the remote branch or nil if it doen't exist
|
|
133
|
+
def remote_branch(repo)
|
|
134
|
+
head_branch = repo.branches[repo.head.name]
|
|
135
|
+
repo.branches['origin/' + head_branch.name]
|
|
136
|
+
end
|
|
100
137
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'slack_ruby_client'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'net/http'
|
|
2
4
|
|
|
3
5
|
# defaults to posting a diff, if messageformat is supplied them a message will be posted too
|
|
4
6
|
# diff defaults to true
|
|
@@ -9,39 +11,73 @@ class SlackDiff < Oxidized::Hook
|
|
|
9
11
|
raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')
|
|
10
12
|
end
|
|
11
13
|
|
|
14
|
+
def slack_upload(client, title, content, channel, proxy)
|
|
15
|
+
logger.info "Posting diff as snippet to #{channel}"
|
|
16
|
+
upload_dest = client.files_getUploadURLExternal(filename: "change",
|
|
17
|
+
length: content.length,
|
|
18
|
+
snippet_type: "diff")
|
|
19
|
+
file_uri = URI.parse(upload_dest[:upload_url])
|
|
20
|
+
|
|
21
|
+
proxy_uri = URI.parse(proxy) if proxy
|
|
22
|
+
proxy_address = proxy_uri ? proxy_uri.host : :ENV
|
|
23
|
+
proxy_port = proxy_uri&.port
|
|
24
|
+
proxy_user = proxy_uri&.user
|
|
25
|
+
proxy_pass = proxy_uri&.password
|
|
26
|
+
|
|
27
|
+
http = Net::HTTP.new(file_uri.host, file_uri.port, proxy_address, proxy_port, proxy_user, proxy_pass)
|
|
28
|
+
http.use_ssl = true
|
|
29
|
+
|
|
30
|
+
request = Net::HTTP::Post.new(file_uri.request_uri, { Host: file_uri.host })
|
|
31
|
+
request.body = content
|
|
32
|
+
response = http.request(request)
|
|
33
|
+
|
|
34
|
+
raise 'Slack file upload failed' unless response.is_a? Net::HTTPSuccess
|
|
35
|
+
|
|
36
|
+
files = [{
|
|
37
|
+
id: upload_dest[:file_id],
|
|
38
|
+
title: title
|
|
39
|
+
}]
|
|
40
|
+
begin
|
|
41
|
+
client.files_completeUploadExternal(channel_id: channel,
|
|
42
|
+
files: files.to_json)
|
|
43
|
+
rescue Slack::Web::Api::Errors::NotInChannel
|
|
44
|
+
logger.info "Not in specified channel, attempting to join"
|
|
45
|
+
client.conversations_join(channel: channel)
|
|
46
|
+
client.files_completeUploadExternal(channel_id: channel,
|
|
47
|
+
files: files.to_json)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
12
51
|
def run_hook(ctx)
|
|
13
52
|
return unless ctx.node
|
|
14
53
|
return unless ctx.event.to_s == "post_store"
|
|
15
54
|
|
|
16
|
-
|
|
55
|
+
logger.info "Connecting to slack"
|
|
17
56
|
Slack::Web::Client.configure do |config|
|
|
18
57
|
config.token = cfg.token
|
|
19
58
|
config.proxy = cfg.proxy if cfg.has_key?('proxy')
|
|
20
59
|
end
|
|
21
60
|
client = Slack::Web::Client.new
|
|
22
61
|
client.auth_test
|
|
23
|
-
|
|
62
|
+
logger.info "Connected"
|
|
24
63
|
if cfg.has_key?("diff") ? cfg.diff : true
|
|
25
64
|
gitoutput = ctx.node.output.new
|
|
26
65
|
diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
|
|
27
66
|
unless diff == "no diffs"
|
|
28
67
|
title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
|
|
29
|
-
|
|
30
|
-
client
|
|
31
|
-
content: diff[:patch].lines.to_a[4..-1].join,
|
|
32
|
-
filetype: "diff",
|
|
33
|
-
title: title,
|
|
34
|
-
filename: "change")
|
|
68
|
+
content = diff[:patch].lines.to_a[4..-1].join
|
|
69
|
+
slack_upload(client, title, content, cfg.channel, cfg.has_key?('proxy') ? cfg.proxy : nil)
|
|
35
70
|
end
|
|
36
71
|
end
|
|
37
72
|
# message custom formatted - optional
|
|
38
73
|
if cfg.message?
|
|
39
|
-
|
|
40
|
-
msg = cfg.message % { node: ctx.node.name.to_s, group: ctx.node.group.to_s, commitref: ctx.commitref,
|
|
41
|
-
|
|
42
|
-
|
|
74
|
+
logger.info cfg.message
|
|
75
|
+
msg = cfg.message % { node: ctx.node.name.to_s, group: ctx.node.group.to_s, commitref: ctx.commitref,
|
|
76
|
+
model: ctx.node.model.class.name.to_s.downcase }
|
|
77
|
+
logger.info msg
|
|
78
|
+
logger.info "Posting message to #{cfg.channel}"
|
|
43
79
|
client.chat_postMessage(channel: cfg.channel, text: msg, as_user: true)
|
|
44
80
|
end
|
|
45
|
-
|
|
81
|
+
logger.info "Finished"
|
|
46
82
|
end
|
|
47
83
|
end
|
|
@@ -5,35 +5,35 @@ class XMPPDiff < Oxidized::Hook
|
|
|
5
5
|
def connect
|
|
6
6
|
@client = Jabber::Client.new(Jabber::JID.new(cfg.jid))
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
logger.info "Connecting to XMPP"
|
|
9
9
|
begin
|
|
10
10
|
Timeout.timeout(15) do
|
|
11
11
|
begin
|
|
12
12
|
@client.connect
|
|
13
13
|
rescue StandardError => e
|
|
14
|
-
|
|
14
|
+
logger.info "Failed to connect to XMPP: #{e}"
|
|
15
15
|
end
|
|
16
16
|
sleep 1
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
logger.info "Authenticating to XMPP"
|
|
19
19
|
@client.auth(cfg.password)
|
|
20
20
|
sleep 1
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
logger.info "Connected to XMPP"
|
|
23
23
|
|
|
24
24
|
@muc = Jabber::MUC::SimpleMUCClient.new(@client)
|
|
25
25
|
@muc.join(cfg.channel + "/" + cfg.nick)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
logger.info "Joined #{cfg.channel}"
|
|
28
28
|
end
|
|
29
29
|
rescue Timeout::Error
|
|
30
|
-
|
|
30
|
+
logger.info "timed out"
|
|
31
31
|
@client = nil
|
|
32
32
|
@muc = nil
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
@client.on_exception do
|
|
36
|
-
|
|
36
|
+
logger.info "XMPP connection aborted, reconnecting"
|
|
37
37
|
@client = nil
|
|
38
38
|
@muc = nil
|
|
39
39
|
connect
|
|
@@ -66,14 +66,14 @@ class XMPPDiff < Oxidized::Hook
|
|
|
66
66
|
# Maybe connecting failed, so only proceed if we actually joined the MUC
|
|
67
67
|
unless @muc.nil?
|
|
68
68
|
title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
|
|
69
|
-
|
|
69
|
+
logger.info "Posting diff as snippet to #{cfg.channel}"
|
|
70
70
|
|
|
71
71
|
@muc.say(title + "\n\n" + diff[:patch].lines.to_a[4..-1].join)
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
rescue Timeout::Error
|
|
76
|
-
|
|
76
|
+
logger.info "timed out"
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
end
|
data/lib/oxidized/hook.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
module Oxidized
|
|
2
2
|
class HookManager
|
|
3
|
+
include SemanticLogger::Loggable
|
|
4
|
+
|
|
3
5
|
class << self
|
|
4
6
|
def from_config(cfg)
|
|
5
7
|
mgr = new
|
|
6
8
|
cfg.hooks.each do |name, h_cfg|
|
|
9
|
+
raise("Please specify an hook type in the configuration") unless h_cfg.type?
|
|
10
|
+
|
|
7
11
|
h_cfg.events.each do |event|
|
|
8
12
|
mgr.register event.to_sym, name, h_cfg.type, h_cfg
|
|
9
13
|
end
|
|
@@ -12,18 +16,24 @@ module Oxidized
|
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
# HookContext is passed to each hook. It
|
|
16
|
-
#
|
|
17
|
-
|
|
19
|
+
# HookContext is passed to each hook. It always carries the event name.
|
|
20
|
+
# The keyword_init: true argument forces keyword-argument initialization.
|
|
21
|
+
HookContext = Struct.new(
|
|
22
|
+
:event, :node, :job, :commitref,
|
|
23
|
+
:node_raw, # raw source record: JSON hash, SQL row hash, CSV field array
|
|
24
|
+
:context, # self from call site, to access methods/bindings of call site
|
|
25
|
+
keyword_init: true
|
|
26
|
+
)
|
|
18
27
|
|
|
19
28
|
# RegisteredHook is a container for a Hook instance
|
|
20
29
|
RegisteredHook = Struct.new(:name, :hook)
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
EVENTS = %i[
|
|
23
32
|
node_success
|
|
24
33
|
node_fail
|
|
25
34
|
post_store
|
|
26
35
|
nodes_done
|
|
36
|
+
source_node_transform
|
|
27
37
|
].freeze
|
|
28
38
|
attr_reader :registered_hooks
|
|
29
39
|
|
|
@@ -32,9 +42,9 @@ module Oxidized
|
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def register(event, name, hook_type, cfg)
|
|
35
|
-
unless
|
|
45
|
+
unless EVENTS.include? event
|
|
36
46
|
raise ArgumentError,
|
|
37
|
-
"unknown event #{event}, available: #{
|
|
47
|
+
"unknown event #{event}, available: #{EVENTS.join ','}"
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
Oxidized.mgr.add_hook(hook_type) || raise("cannot load hook '#{hook_type}', not found")
|
|
@@ -47,24 +57,70 @@ module Oxidized
|
|
|
47
57
|
hook.cfg = cfg
|
|
48
58
|
|
|
49
59
|
@registered_hooks[event] << RegisteredHook.new(name, hook)
|
|
50
|
-
|
|
60
|
+
logger.debug "Hook #{name.inspect} registered #{hook.class} for event #{event.inspect}"
|
|
51
61
|
end
|
|
52
62
|
|
|
63
|
+
# --- Transform events ---
|
|
64
|
+
|
|
65
|
+
# Runs source_node_transform hooks in sequence, passing the return value of
|
|
66
|
+
# each hook as node to the next. Returns the final node, or nil
|
|
67
|
+
# to signal that the node should be excluded.
|
|
68
|
+
def source_node_transform(node:, node_raw:, context:)
|
|
69
|
+
ctx = HookContext.new(
|
|
70
|
+
event: :source_node_transform,
|
|
71
|
+
node: node,
|
|
72
|
+
node_raw: node_raw,
|
|
73
|
+
context: context
|
|
74
|
+
)
|
|
75
|
+
@registered_hooks[:source_node_transform].each do |r_hook|
|
|
76
|
+
ctx.node = r_hook.hook.run_hook(ctx)
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
logger.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " \
|
|
79
|
+
"(#{e.inspect}) for event :source_node_transform"
|
|
80
|
+
end
|
|
81
|
+
ctx.node
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# --- Fire-and-forget events ---
|
|
85
|
+
|
|
86
|
+
def node_success(node:, job: nil)
|
|
87
|
+
handle(:node_success, node: node, job: job)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def node_fail(node:, job: nil)
|
|
91
|
+
handle(:node_fail, node: node, job: job)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def post_store(node:, job: nil, commitref: nil)
|
|
95
|
+
handle(:post_store, node: node, job: job, commitref: commitref)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def nodes_done
|
|
99
|
+
handle(:nodes_done)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Shared implementation for fire-and-forget events: runs all registered
|
|
105
|
+
# hooks for the event, ignores return values, logs errors.
|
|
53
106
|
def handle(event, ctx_params = {})
|
|
54
|
-
ctx = HookContext.new ctx_params
|
|
55
|
-
ctx.event = event
|
|
107
|
+
ctx = HookContext.new(event: event, **ctx_params)
|
|
56
108
|
|
|
57
109
|
@registered_hooks[event].each do |r_hook|
|
|
58
|
-
r_hook.hook.run_hook
|
|
110
|
+
r_hook.hook.run_hook(ctx)
|
|
59
111
|
rescue StandardError => e
|
|
60
|
-
|
|
61
|
-
|
|
112
|
+
logger.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " \
|
|
113
|
+
"(#{e.inspect}) for event #{event.inspect}"
|
|
62
114
|
end
|
|
115
|
+
|
|
116
|
+
nil
|
|
63
117
|
end
|
|
64
118
|
end
|
|
65
119
|
|
|
66
120
|
# Hook abstract base class
|
|
67
121
|
class Hook
|
|
122
|
+
include SemanticLogger::Loggable
|
|
123
|
+
|
|
68
124
|
attr_reader :cfg
|
|
69
125
|
|
|
70
126
|
def cfg=(cfg)
|
|
@@ -75,9 +131,5 @@ module Oxidized
|
|
|
75
131
|
def run_hook(_ctx)
|
|
76
132
|
raise NotImplementedError
|
|
77
133
|
end
|
|
78
|
-
|
|
79
|
-
def log(msg, level = :info)
|
|
80
|
-
Oxidized.logger.send(level, "#{self.class.name}: #{msg}")
|
|
81
|
-
end
|
|
82
134
|
end
|
|
83
135
|
end
|
data/lib/oxidized/input/cli.rb
CHANGED
|
@@ -20,15 +20,16 @@ module Oxidized
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def connect_cli
|
|
23
|
-
|
|
23
|
+
logger.debug "Running post_login commands at #{node.name}"
|
|
24
24
|
@post_login.each do |command, block|
|
|
25
|
-
|
|
25
|
+
logger.debug "Running post_login command: #{command.inspect}, " \
|
|
26
|
+
"block: #{block.inspect} at #{node.name}"
|
|
26
27
|
block ? block.call : (cmd command)
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def disconnect_cli
|
|
31
|
-
|
|
32
|
+
logger.debug "Running pre_logout commands at #{node.name}"
|
|
32
33
|
@pre_logout.each { |command, block| block ? block.call : (cmd command, nil) }
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -52,6 +53,10 @@ module Oxidized
|
|
|
52
53
|
@password || (@password = regex)
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
def newline(newline_str = "\n")
|
|
57
|
+
@newline || (@newline = newline_str)
|
|
58
|
+
end
|
|
59
|
+
|
|
55
60
|
def login
|
|
56
61
|
match_re = [@node.prompt]
|
|
57
62
|
match_re << @username if @username
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Oxidized
|
|
2
|
+
class DebugText
|
|
3
|
+
include SemanticLogger::Loggable
|
|
4
|
+
|
|
5
|
+
def initialize(config_debug, node, input_name)
|
|
6
|
+
return unless config_debug == true ||
|
|
7
|
+
(config_debug.is_a?(String) && config_debug.downcase.include?('text'))
|
|
8
|
+
|
|
9
|
+
@log = File.open(logfile(node, input_name), 'w')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Separate method to ease unit tests
|
|
13
|
+
def logfile(node, input_name)
|
|
14
|
+
timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
|
|
15
|
+
file = Oxidized::Config::LOG + "/#{node&.ip}-#{input_name}-#{timestamp}.txt"
|
|
16
|
+
logger.debug "Writing I/O Debugging to #{file}"
|
|
17
|
+
file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def send_data(data)
|
|
21
|
+
return unless @log
|
|
22
|
+
|
|
23
|
+
@log.puts "sent cmd #{data.dump}"
|
|
24
|
+
@log.flush
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def receive_data(data)
|
|
28
|
+
return unless @log
|
|
29
|
+
|
|
30
|
+
@log.puts "received #{data.dump}"
|
|
31
|
+
@log.flush
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def close
|
|
35
|
+
return unless @log
|
|
36
|
+
|
|
37
|
+
@log.close
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Oxidized
|
|
2
|
+
class DebugYAML
|
|
3
|
+
include SemanticLogger::Loggable
|
|
4
|
+
|
|
5
|
+
def initialize(config_debug, node, input_name)
|
|
6
|
+
return unless config_debug == true ||
|
|
7
|
+
(config_debug.is_a?(String) && config_debug.downcase.include?('yaml'))
|
|
8
|
+
|
|
9
|
+
@log = File.open(logfile(node, input_name), 'w')
|
|
10
|
+
|
|
11
|
+
@partial_line = false
|
|
12
|
+
@first_line = true
|
|
13
|
+
@commands_started = false
|
|
14
|
+
|
|
15
|
+
@log.puts '---'
|
|
16
|
+
@log.puts 'init_prompt: |-'
|
|
17
|
+
@log.flush
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Separate method to ease unit tests
|
|
21
|
+
def logfile(node, input_name)
|
|
22
|
+
timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
|
|
23
|
+
file = Oxidized::Config::LOG + "/#{node&.ip}-#{input_name}-#{timestamp}.yaml"
|
|
24
|
+
logger.debug "Writing YAML Simulation to #{file}"
|
|
25
|
+
file
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def send_data(data)
|
|
29
|
+
return unless @log
|
|
30
|
+
|
|
31
|
+
@log.puts
|
|
32
|
+
@log.puts 'commands:' unless @commands_started
|
|
33
|
+
@log.puts " - #{data.dump}: |-"
|
|
34
|
+
@first_line = true
|
|
35
|
+
@partial_line = false
|
|
36
|
+
@commands_started = true
|
|
37
|
+
@log.flush
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def receive_data(data)
|
|
41
|
+
return unless @log
|
|
42
|
+
return if data.empty?
|
|
43
|
+
|
|
44
|
+
lines = data.split("\n", -1)
|
|
45
|
+
|
|
46
|
+
lines.each_with_index do |line, idx|
|
|
47
|
+
is_last = idx == lines.length - 1
|
|
48
|
+
full_line = is_last ? (data[-1] == "\n") : true
|
|
49
|
+
# Escape line and strip surrounding double quotes
|
|
50
|
+
line = line.dump[1..-2]
|
|
51
|
+
if @first_line
|
|
52
|
+
# Make sure the leading space of the first line (if present)
|
|
53
|
+
# is coded with \0x20 or YAML block scalars won't work
|
|
54
|
+
line.sub!(/^ /, '\x20')
|
|
55
|
+
@first_line = false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Make sure trailing white spaces are coded with \0x20
|
|
59
|
+
line.gsub!(/ $/, '\x20')
|
|
60
|
+
|
|
61
|
+
output = @partial_line ? line : (' ' + line)
|
|
62
|
+
@partial_line = false
|
|
63
|
+
|
|
64
|
+
if full_line
|
|
65
|
+
@log.puts output
|
|
66
|
+
else
|
|
67
|
+
@log.write output
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@partial_line = data[-1] != "\n"
|
|
72
|
+
|
|
73
|
+
@log.flush
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def close
|
|
77
|
+
return unless @log
|
|
78
|
+
|
|
79
|
+
@log.close
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/oxidized/input/exec.rb
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
module Oxidized
|
|
2
|
-
require "oxidized/input/cli"
|
|
3
|
-
|
|
4
2
|
class Exec < Input
|
|
5
|
-
include Input::CLI
|
|
6
|
-
|
|
7
3
|
def connect(node)
|
|
8
4
|
@node = node
|
|
9
|
-
@log = File.open(Oxidized::Config::Log + "/#{@node.ip}-exec", "w") if Oxidized.config.input.debug?
|
|
10
5
|
@node.model.cfg["exec"].each { |cb| instance_exec(&cb) }
|
|
11
6
|
end
|
|
12
7
|
|
|
13
8
|
def cmd(cmd_str)
|
|
14
|
-
|
|
9
|
+
logger.debug "EXEC: #{cmd_str} @ #{@node.name}"
|
|
15
10
|
# I'd really like to do popen3 with separate arguments, but that would
|
|
16
11
|
# require refactoring cmd to take parameters
|
|
17
12
|
%x(#{cmd_str})
|
|
@@ -19,10 +14,6 @@ module Oxidized
|
|
|
19
14
|
|
|
20
15
|
private
|
|
21
16
|
|
|
22
|
-
def disconnect
|
|
23
|
-
true
|
|
24
|
-
ensure
|
|
25
|
-
@log.close if Oxidized.config.input.debug?
|
|
26
|
-
end
|
|
17
|
+
def disconnect; end
|
|
27
18
|
end
|
|
28
19
|
end
|
data/lib/oxidized/input/ftp.rb
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
module Oxidized
|
|
2
2
|
require 'net/ftp'
|
|
3
3
|
require 'timeout'
|
|
4
|
-
require_relative 'cli'
|
|
5
|
-
|
|
6
4
|
class FTP < Input
|
|
7
|
-
|
|
8
|
-
debug: [
|
|
9
|
-
# Net::SSH::Disconnect,
|
|
10
|
-
],
|
|
11
|
-
warn: [
|
|
12
|
-
# RuntimeError,
|
|
13
|
-
# Net::SSH::AuthenticationFailed,
|
|
14
|
-
]
|
|
15
|
-
}.freeze
|
|
16
|
-
include Input::CLI
|
|
17
|
-
|
|
18
|
-
def connect(node)
|
|
5
|
+
def connect(node) # rubocop:disable Naming/PredicateMethod
|
|
19
6
|
@node = node
|
|
20
7
|
@node.model.cfg['ftp'].each { |cb| instance_exec(&cb) }
|
|
21
|
-
@log = File.open(Oxidized::Config::Log + "/#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
|
|
22
8
|
@ftp = Net::FTP.new(@node.ip)
|
|
23
9
|
@ftp.passive = Oxidized.config.input.ftp.passive
|
|
24
10
|
@ftp.login @node.auth[:username], @node.auth[:password]
|
|
@@ -30,7 +16,7 @@ module Oxidized
|
|
|
30
16
|
end
|
|
31
17
|
|
|
32
18
|
def cmd(file)
|
|
33
|
-
|
|
19
|
+
logger.debug "FTP: #{file} @ #{@node.name}"
|
|
34
20
|
@ftp.getbinaryfile file, nil
|
|
35
21
|
end
|
|
36
22
|
|
|
@@ -47,9 +33,6 @@ module Oxidized
|
|
|
47
33
|
|
|
48
34
|
def disconnect
|
|
49
35
|
@ftp.close
|
|
50
|
-
# rescue Errno::ECONNRESET, IOError
|
|
51
|
-
ensure
|
|
52
|
-
@log.close if Oxidized.config.input.debug?
|
|
53
36
|
end
|
|
54
37
|
end
|
|
55
38
|
end
|