oxidized 0.30.1 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +3 -4
  3. data/.github/workflows/stale.yml +4 -2
  4. data/.rubocop.yml +18 -3
  5. data/.rubocop_todo.yml +4 -11
  6. data/CHANGELOG.md +93 -1
  7. data/CONTRIBUTING.md +5 -0
  8. data/Dockerfile +84 -20
  9. data/README.md +5 -21
  10. data/Rakefile +31 -2
  11. data/docs/Configuration.md +50 -14
  12. data/docs/Creating-Models.md +75 -4
  13. data/docs/DeviceSimulation.md +184 -0
  14. data/docs/Hooks.md +39 -5
  15. data/docs/Issues.md +97 -0
  16. data/docs/Model-Notes/APC_AOS.md +29 -16
  17. data/docs/Model-Notes/Cumulus.md +5 -0
  18. data/docs/Model-Notes/FSOS.md +6 -0
  19. data/docs/Model-Notes/FortiOS.md +21 -5
  20. data/docs/Model-Notes/HPEAruba.md +31 -0
  21. data/docs/Model-Notes/OS6.md +10 -0
  22. data/docs/Model-Notes/RouterOS.md +15 -0
  23. data/docs/Model-Notes/SikluMHTG.md +7 -0
  24. data/docs/ModelUnitTests.md +186 -0
  25. data/docs/Outputs.md +2 -0
  26. data/docs/Release.md +18 -15
  27. data/docs/Sources.md +21 -0
  28. data/docs/Supported-OS-Types.md +14 -7
  29. data/docs/Troubleshooting.md +35 -0
  30. data/examples/podman-compose/Makefile +59 -17
  31. data/examples/podman-compose/README.md +63 -27
  32. data/examples/podman-compose/docker-compose.yml +11 -2
  33. data/examples/podman-compose/gitserver/.gitignore +1 -0
  34. data/examples/podman-compose/gitserver/Dockerfile +14 -0
  35. data/examples/podman-compose/model-simulation/Dockerfile-model +1 -1
  36. data/examples/podman-compose/model-simulation/asternos.sh +2 -0
  37. data/examples/podman-compose/oxidized-config/.gitignore +2 -0
  38. data/examples/podman-compose/oxidized-config/config +1 -1
  39. data/examples/podman-compose/oxidized-config/config_csv-file +46 -0
  40. data/examples/podman-compose/oxidized-config/config_csv-gitserver +56 -0
  41. data/examples/podman-compose/oxidized-ssh/.gitignore +1 -0
  42. data/extra/device2yaml.rb +245 -0
  43. data/extra/gitdiff-msteams.sh +32 -5
  44. data/extra/nagios_check_failing_nodes.rb +1 -1
  45. data/extra/rest_client.rb +1 -1
  46. data/lib/oxidized/config.rb +8 -2
  47. data/lib/oxidized/hook/githubrepo.rb +37 -7
  48. data/lib/oxidized/hook/slackdiff.rb +29 -7
  49. data/lib/oxidized/input/http.rb +1 -0
  50. data/lib/oxidized/input/ssh.rb +13 -5
  51. data/lib/oxidized/input/telnet.rb +1 -1
  52. data/lib/oxidized/manager.rb +17 -16
  53. data/lib/oxidized/model/aos7.rb +2 -0
  54. data/lib/oxidized/model/aoscx.rb +16 -2
  55. data/lib/oxidized/model/aosw.rb +8 -2
  56. data/lib/oxidized/model/apc_aos.rb +1 -1
  57. data/lib/oxidized/model/arubainstant.rb +90 -0
  58. data/lib/oxidized/model/asa.rb +2 -1
  59. data/lib/oxidized/model/asyncos.rb +1 -1
  60. data/lib/oxidized/model/audiocodes.rb +2 -2
  61. data/lib/oxidized/model/cnos.rb +13 -10
  62. data/lib/oxidized/model/cumulus.rb +19 -2
  63. data/lib/oxidized/model/dlink.rb +1 -0
  64. data/lib/oxidized/model/dlinknextgen.rb +3 -0
  65. data/lib/oxidized/model/edgecos.rb +2 -1
  66. data/lib/oxidized/model/enterprise_sonic.rb +46 -0
  67. data/lib/oxidized/model/eos.rb +2 -0
  68. data/lib/oxidized/model/f5os.rb +17 -0
  69. data/lib/oxidized/model/firewareos.rb +10 -1
  70. data/lib/oxidized/model/fortios.rb +24 -1
  71. data/lib/oxidized/model/fsos.rb +5 -1
  72. data/lib/oxidized/model/garderos.rb +43 -0
  73. data/lib/oxidized/model/h3c.rb +1 -1
  74. data/lib/oxidized/model/ibos.rb +1 -0
  75. data/lib/oxidized/model/ios.rb +20 -12
  76. data/lib/oxidized/model/iosxr.rb +1 -1
  77. data/lib/oxidized/model/junos.rb +1 -1
  78. data/lib/oxidized/model/kornfeldos.rb +33 -0
  79. data/lib/oxidized/model/lenovonos.rb +2 -0
  80. data/lib/oxidized/model/linuxgeneric.rb +1 -1
  81. data/lib/oxidized/model/model.rb +2 -2
  82. data/lib/oxidized/model/netgear.rb +1 -1
  83. data/lib/oxidized/model/nodegrid.rb +1 -1
  84. data/lib/oxidized/model/nsxdfw.rb +30 -0
  85. data/lib/oxidized/model/nxos.rb +2 -1
  86. data/lib/oxidized/model/os6.rb +48 -0
  87. data/lib/oxidized/model/rgos.rb +1 -1
  88. data/lib/oxidized/model/riverbed.rb +104 -0
  89. data/lib/oxidized/model/routeros.rb +2 -2
  90. data/lib/oxidized/model/saos.rb +18 -1
  91. data/lib/oxidized/model/siklumhtg.rb +22 -0
  92. data/lib/oxidized/model/sonicos.rb +8 -2
  93. data/lib/oxidized/model/tplink.rb +1 -0
  94. data/lib/oxidized/model/uplinkolt.rb +46 -0
  95. data/lib/oxidized/model/vyatta.rb +2 -2
  96. data/lib/oxidized/model/xos.rb +7 -0
  97. data/lib/oxidized/node.rb +30 -18
  98. data/lib/oxidized/nodes.rb +13 -5
  99. data/lib/oxidized/output/file.rb +45 -42
  100. data/lib/oxidized/output/git.rb +185 -160
  101. data/lib/oxidized/output/gitcrypt.rb +188 -186
  102. data/lib/oxidized/output/http.rb +53 -51
  103. data/lib/oxidized/output/output.rb +6 -4
  104. data/lib/oxidized/source/csv.rb +44 -49
  105. data/lib/oxidized/source/http.rb +63 -81
  106. data/lib/oxidized/source/jsonfile.rb +63 -0
  107. data/lib/oxidized/source/source.rb +73 -18
  108. data/lib/oxidized/source/sql.rb +66 -59
  109. data/lib/oxidized/version.rb +2 -2
  110. data/oxidized.gemspec +25 -18
  111. metadata +115 -21
@@ -1,195 +1,220 @@
1
1
  module Oxidized
2
- class Git < Output
3
- using Refinements
4
-
5
- class GitError < OxidizedError; end
6
- begin
7
- require 'rugged'
8
- rescue LoadError
9
- raise OxidizedError, 'rugged not found: sudo gem install rugged'
10
- end
2
+ module Output
3
+ class Git < Output
4
+ using Refinements
11
5
 
12
- attr_reader :commitref
6
+ class GitError < OxidizedError; end
7
+ begin
8
+ require 'rugged'
9
+ rescue LoadError
10
+ raise OxidizedError, 'rugged not found: sudo gem install rugged'
11
+ end
13
12
 
14
- def initialize
15
- super
16
- @cfg = Oxidized.config.output.git
17
- end
13
+ attr_reader :commitref
18
14
 
19
- def setup
20
- if @cfg.empty?
21
- Oxidized.asetus.user.output.git.user = 'Oxidized'
22
- Oxidized.asetus.user.output.git.email = 'o@example.com'
23
- Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git')
24
- Oxidized.asetus.save :user
25
- raise NoConfig, 'no output git config, edit ~/.config/oxidized/config'
15
+ def initialize
16
+ super
17
+ @cfg = Oxidized.config.output.git
26
18
  end
27
19
 
28
- if @cfg.repo.respond_to?(:each)
29
- @cfg.repo.each do |group, repo|
30
- @cfg.repo["#{group}="] = File.expand_path repo
20
+ def setup
21
+ if @cfg.empty?
22
+ Oxidized.asetus.user.output.git.user = 'Oxidized'
23
+ Oxidized.asetus.user.output.git.email = 'o@example.com'
24
+ Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git')
25
+ Oxidized.asetus.save :user
26
+ raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}"
31
27
  end
32
- else
33
- @cfg.repo = File.expand_path @cfg.repo
34
- end
35
- end
36
28
 
37
- def store(file, outputs, opt = {})
38
- @msg = opt[:msg]
39
- @user = opt[:user] || @cfg.user
40
- @email = opt[:email] || @cfg.email
41
- @opt = opt
42
- @commitref = nil
43
- repo = @cfg.repo
44
-
45
- outputs.types.each do |type|
46
- type_cfg = ''
47
- type_repo = File.join(File.dirname(repo), type + '.git')
48
- outputs.type(type).each do |output|
49
- (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
50
- type_file = file + '--' + output.name
51
- if @cfg.type_as_directory?
52
- type_file = type + '/' + type_file
53
- type_repo = repo
29
+ if @cfg.repo.respond_to?(:each)
30
+ @cfg.repo.each do |group, repo|
31
+ @cfg.repo["#{group}="] = File.expand_path repo
54
32
  end
55
- update type_repo, type_file, output
33
+ else
34
+ @cfg.repo = File.expand_path @cfg.repo
56
35
  end
57
- update type_repo, file, type_cfg
58
36
  end
59
37
 
60
- update repo, file, outputs.to_cfg
61
- end
38
+ def store(file, outputs, opt = {})
39
+ @msg = opt[:msg]
40
+ @user = opt[:user] || @cfg.user
41
+ @email = opt[:email] || @cfg.email
42
+ @opt = opt
43
+ @commitref = nil
44
+ repo = @cfg.repo
45
+
46
+ outputs.types.each do |type|
47
+ type_cfg = ''
48
+ type_repo = File.join(File.dirname(repo), type + '.git')
49
+ outputs.type(type).each do |output|
50
+ (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
51
+ type_file = file + '--' + output.name
52
+ if @cfg.type_as_directory?
53
+ type_file = type + '/' + type_file
54
+ type_repo = repo
55
+ end
56
+ update type_repo, type_file, output
57
+ end
58
+ update type_repo, file, type_cfg
59
+ end
62
60
 
63
- def fetch(node, group)
64
- repo, path = yield_repo_and_path(node, group)
65
- repo = Rugged::Repository.new repo
66
- index = repo.index
67
- index.read_tree repo.head.target.tree unless repo.empty?
68
- repo.read(index.get(path)[:oid]).data
69
- rescue StandardError
70
- 'node not found'
71
- end
61
+ update repo, file, outputs.to_cfg
62
+ end
72
63
 
73
- # give a hash of all oid revision for the given node, and the date of the commit
74
- def version(node, group)
75
- repo, path = yield_repo_and_path(node, group)
76
-
77
- repo = Rugged::Repository.new repo
78
- walker = Rugged::Walker.new(repo)
79
- walker.sorting(Rugged::SORT_DATE)
80
- walker.push(repo.head.target.oid)
81
- i = -1
82
- tab = []
83
- walker.each do |commit|
84
- # Diabled rubocop because the suggested .empty? does not work here.
85
- next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate
86
-
87
- hash = {}
88
- hash[:date] = commit.time.to_s
89
- hash[:oid] = commit.oid
90
- hash[:author] = commit.author
91
- hash[:message] = commit.message
92
- tab[i += 1] = hash
64
+ # Returns the configuration of group/node_name
65
+ #
66
+ # #fetch is called by Nodes#fetch
67
+ # Nodes#fetch creates a new Output object each time, so we cannot
68
+ # store the repo index in memory. But as we keep the repo index up
69
+ # to date on disk in #update_repo, we can read it from disk instead of
70
+ # rebuilding it each time.
71
+ def fetch(node, group)
72
+ repo, path = yield_repo_and_path(node, group)
73
+ repo = Rugged::Repository.new repo
74
+ # Read the index from disk
75
+ index = repo.index
76
+
77
+ repo.read(index.get(path)[:oid]).data
78
+ rescue StandardError
79
+ 'node not found'
93
80
  end
94
- walker.reset
95
- tab
96
- rescue StandardError
97
- 'node not found'
98
- end
99
81
 
100
- # give the blob of a specific revision
101
- def get_version(node, group, oid)
102
- repo, path = yield_repo_and_path(node, group)
103
- repo = Rugged::Repository.new repo
104
- repo.blob_at(oid, path).content
105
- rescue StandardError
106
- 'version not found'
107
- end
82
+ # give a hash of all oid revision for the given node, and the date of the commit
83
+ def version(node, group)
84
+ repo, path = yield_repo_and_path(node, group)
108
85
 
109
- # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines)
110
- def get_diff(node, group, oid1, oid2)
111
- diff_commits = nil
112
- repo, = yield_repo_and_path(node, group)
113
- repo = Rugged::Repository.new repo
114
- commit = repo.lookup(oid1)
115
-
116
- if oid2
117
- commit_old = repo.lookup(oid2)
118
- diff = repo.diff(commit_old, commit)
119
- diff.each do |patch|
120
- if /#{node.name}\s+/ =~ patch.to_s.lines.first
121
- diff_commits = { patch: patch.to_s, stat: patch.stat }
122
- break
123
- end
86
+ repo = Rugged::Repository.new repo
87
+ walker = Rugged::Walker.new(repo)
88
+ walker.sorting(Rugged::SORT_DATE)
89
+ walker.push(repo.head.target.oid)
90
+ i = -1
91
+ tab = []
92
+ walker.each do |commit|
93
+ # Diabled rubocop because the suggested .empty? does not work here.
94
+ next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate
95
+
96
+ hash = {}
97
+ hash[:date] = commit.time.to_s
98
+ hash[:oid] = commit.oid
99
+ hash[:author] = commit.author
100
+ hash[:message] = commit.message
101
+ tab[i += 1] = hash
124
102
  end
125
- else
126
- stat = commit.parents[0].diff(commit).stat
127
- stat = [stat[1], stat[2]]
128
- patch = commit.parents[0].diff(commit).patch
129
- diff_commits = { patch: patch, stat: stat }
103
+ walker.reset
104
+ tab
105
+ rescue StandardError
106
+ 'node not found'
130
107
  end
131
108
 
132
- diff_commits
133
- rescue StandardError
134
- 'no diffs'
135
- end
109
+ # give the blob of a specific revision
110
+ def get_version(node, group, oid)
111
+ repo, path = yield_repo_and_path(node, group)
112
+ repo = Rugged::Repository.new repo
113
+ repo.blob_at(oid, path).content
114
+ rescue StandardError
115
+ 'version not found'
116
+ end
136
117
 
137
- private
118
+ # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines)
119
+ def get_diff(node, group, oid1, oid2)
120
+ diff_commits = nil
121
+ repo, = yield_repo_and_path(node, group)
122
+ repo = Rugged::Repository.new repo
123
+ commit = repo.lookup(oid1)
124
+
125
+ if oid2
126
+ commit_old = repo.lookup(oid2)
127
+ diff = repo.diff(commit_old, commit)
128
+ diff.each do |patch|
129
+ if /#{node.name}\s+/ =~ patch.to_s.lines.first
130
+ diff_commits = { patch: patch.to_s, stat: patch.stat }
131
+ break
132
+ end
133
+ end
134
+ else
135
+ stat = commit.parents[0].diff(commit).stat
136
+ stat = [stat[1], stat[2]]
137
+ patch = commit.parents[0].diff(commit).patch
138
+ diff_commits = { patch: patch, stat: stat }
139
+ end
138
140
 
139
- def yield_repo_and_path(node, group)
140
- repo, path = node.repo, node.name
141
+ diff_commits
142
+ rescue StandardError
143
+ 'no diffs'
144
+ end
141
145
 
142
- path = "#{group}/#{node.name}" if group && @cfg.single_repo?
146
+ private
143
147
 
144
- [repo, path]
145
- end
148
+ def yield_repo_and_path(node, group)
149
+ repo, path = node.repo, node.name
146
150
 
147
- def update(repo, file, data)
148
- return if data.empty?
151
+ path = "#{group}/#{node.name}" if group && !group.empty? && @cfg.single_repo?
149
152
 
150
- if @opt[:group]
151
- if @cfg.single_repo?
152
- file = File.join @opt[:group], file
153
- else
154
- repo = if repo.is_a?(::String)
155
- File.join File.dirname(repo), @opt[:group] + '.git'
156
- else
157
- repo[@opt[:group]]
158
- end
159
- end
153
+ [repo, path]
160
154
  end
161
155
 
162
- begin
163
- repo = Rugged::Repository.new repo
164
- update_repo repo, file, data
165
- rescue Rugged::OSError, Rugged::RepositoryError => e
156
+ def update(repo, file, data)
157
+ return if data.empty?
158
+
159
+ if @opt[:group]
160
+ if @cfg.single_repo?
161
+ file = File.join @opt[:group], file
162
+ else
163
+ repo = if repo.is_a?(::String)
164
+ File.join File.dirname(repo), @opt[:group] + '.git'
165
+ else
166
+ repo[@opt[:group]]
167
+ end
168
+ end
169
+ end
170
+
166
171
  begin
167
- Rugged::Repository.init_at repo, :bare
168
- rescue StandardError => create_error
169
- raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo"
172
+ repo = Rugged::Repository.new repo
173
+ update_repo repo, file, data
174
+ rescue Rugged::OSError, Rugged::RepositoryError => e
175
+ begin
176
+ Rugged::Repository.init_at repo, :bare
177
+ rescue StandardError => create_error
178
+ raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo"
179
+ end
180
+ retry
170
181
  end
171
- retry
172
182
  end
173
- end
174
-
175
- def update_repo(repo, file, data)
176
- oid_old = repo.blob_at(repo.head.target_id, file) rescue nil
177
- return false if oid_old && (oid_old.content.b == data.b)
178
-
179
- oid = repo.write data, :blob
180
- index = repo.index
181
- index.add path: file, oid: oid, mode: 0o100644
182
183
 
183
- repo.config['user.name'] = @user
184
- repo.config['user.email'] = @email
185
- @commitref = Rugged::Commit.create(repo,
186
- tree: index.write_tree(repo),
187
- message: @msg,
188
- parents: repo.empty? ? [] : [repo.head.target].compact,
189
- update_ref: 'HEAD')
190
-
191
- index.write
192
- true
184
+ # Uploads data into file in the repo
185
+ #
186
+ # @param [String] file: the file to save the configuration to
187
+ # @param [String] data: the configuration to save
188
+ # @param [Rugged::Repository] repo: the git repository to use
189
+ #
190
+ # If Oxidized.config.output.git.single_repo = false (which is the default),
191
+ # there will one repository for each group.
192
+ #
193
+ # update_repo caches the index on disk. An index is usually used in a
194
+ # working directory and not in a bare repository, which confuses users.
195
+ # The alternative would be to rebuild the index each time, which a little
196
+ # time consuming. Caching the index in memory is difficult because a new
197
+ # Output object is created each time #store is called.
198
+ def update_repo(repo, file, data)
199
+ oid_old = repo.blob_at(repo.head.target_id, file) rescue nil
200
+ return false if oid_old && (oid_old.content.b == data.b)
201
+
202
+ oid = repo.write data, :blob
203
+ # Read the index from disk
204
+ index = repo.index
205
+ index.add path: file, oid: oid, mode: 0o100644
206
+
207
+ repo.config['user.name'] = @user
208
+ repo.config['user.email'] = @email
209
+ @commitref = Rugged::Commit.create(repo,
210
+ tree: index.write_tree(repo),
211
+ message: @msg,
212
+ parents: repo.empty? ? [] : [repo.head.target].compact,
213
+ update_ref: 'HEAD')
214
+
215
+ index.write
216
+ true
217
+ end
193
218
  end
194
219
  end
195
220
  end