oxidized 0.32.1 → 0.34.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/.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/publishdocker.yml +35 -16
- data/.github/workflows/ruby.yml +4 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +29 -8
- data/.rubocop_todo.yml +1 -60
- data/CHANGELOG.md +103 -2
- data/CONTRIBUTING.md +20 -10
- data/Dockerfile +37 -64
- data/README.md +47 -141
- data/Rakefile +9 -11
- data/docs/Configuration.md +236 -27
- data/docs/DeviceSimulation.md +19 -7
- data/docs/Docker.md +245 -0
- data/docs/Issues.md +27 -1
- data/docs/Model-Notes/EatonNetwork.md +18 -0
- data/docs/Model-Notes/HPEAruba.md +3 -2
- data/docs/ModelUnitTests.md +35 -25
- data/docs/Outputs.md +83 -2
- data/docs/Release.md +34 -24
- data/docs/Supported-OS-Types.md +7 -0
- data/docs/Troubleshooting.md +4 -13
- data/extra/device2yaml.rb +24 -9
- data/extra/rest_client.rb +3 -2
- data/extra/syslog.rb +8 -3
- data/lib/oxidized/cli.rb +7 -3
- data/lib/oxidized/config/vars.rb +22 -14
- data/lib/oxidized/config.rb +3 -2
- data/lib/oxidized/core.rb +30 -8
- data/lib/oxidized/hook/ciscosparkdiff.rb +11 -9
- data/lib/oxidized/hook/exec.rb +5 -4
- data/lib/oxidized/hook/githubrepo.rb +23 -17
- data/lib/oxidized/hook/noophook.rb +2 -2
- data/lib/oxidized/hook/slackdiff.rb +9 -8
- data/lib/oxidized/hook/xmppdiff.rb +9 -9
- data/lib/oxidized/hook.rb +10 -8
- data/lib/oxidized/input/cli.rb +8 -3
- data/lib/oxidized/input/exec.rb +1 -1
- data/lib/oxidized/input/ftp.rb +2 -2
- data/lib/oxidized/input/http.rb +6 -6
- data/lib/oxidized/input/input.rb +1 -0
- data/lib/oxidized/input/scp.rb +2 -2
- data/lib/oxidized/input/ssh.rb +21 -14
- data/lib/oxidized/input/telnet.rb +3 -3
- data/lib/oxidized/input/tftp.rb +1 -1
- data/lib/oxidized/job.rb +7 -4
- data/lib/oxidized/logger.rb +51 -0
- data/lib/oxidized/model/acos.rb +1 -0
- data/lib/oxidized/model/aos7.rb +9 -0
- data/lib/oxidized/model/aoscx.rb +2 -0
- data/lib/oxidized/model/aosw.rb +22 -17
- data/lib/oxidized/model/aricentiss.rb +2 -2
- data/lib/oxidized/model/asa.rb +3 -3
- data/lib/oxidized/model/awplus.rb +13 -10
- data/lib/oxidized/model/eatonnetwork.rb +65 -0
- data/lib/oxidized/model/edgecos.rb +2 -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 +1 -1
- data/lib/oxidized/model/fabricos.rb +1 -1
- data/lib/oxidized/model/fastiron.rb +3 -1
- data/lib/oxidized/model/firelinuxos.rb +12 -3
- data/lib/oxidized/model/fortios.rb +5 -4
- data/lib/oxidized/model/gaiaos.rb +4 -4
- data/lib/oxidized/model/ingate.rb +47 -0
- data/lib/oxidized/model/ios.rb +16 -5
- data/lib/oxidized/model/ironware.rb +1 -1
- data/lib/oxidized/model/junos.rb +4 -0
- data/lib/oxidized/model/linksyssrw.rb +3 -3
- data/lib/oxidized/model/mlnxos.rb +14 -7
- data/lib/oxidized/model/model.rb +4 -3
- data/lib/oxidized/model/netgear.rb +8 -0
- data/lib/oxidized/model/nsxdfw.rb +2 -1
- data/lib/oxidized/model/nsxfirewall.rb +2 -1
- data/lib/oxidized/model/nxos.rb +2 -2
- data/lib/oxidized/model/openwrt.rb +6 -6
- data/lib/oxidized/model/powerconnect.rb +31 -10
- data/lib/oxidized/model/procurve.rb +3 -1
- data/lib/oxidized/model/qtech.rb +3 -1
- data/lib/oxidized/model/quantaos.rb +8 -6
- data/lib/oxidized/model/routeros.rb +3 -2
- data/lib/oxidized/model/saos10.rb +38 -0
- data/lib/oxidized/model/sixwind.rb +28 -0
- data/lib/oxidized/model/sonicos.rb +1 -1
- data/lib/oxidized/model/srosmd.rb +1 -1
- data/lib/oxidized/model/supermicro.rb +1 -1
- data/lib/oxidized/model/timos.rb +1 -1
- data/lib/oxidized/model/tmos.rb +1 -0
- data/lib/oxidized/model/tnsr.rb +53 -0
- data/lib/oxidized/model/trango.rb +3 -1
- data/lib/oxidized/model/unifiap.rb +144 -0
- data/lib/oxidized/model/vrp.rb +3 -1
- data/lib/oxidized/model/xos.rb +3 -1
- data/lib/oxidized/model/zhoneolt.rb +3 -1
- data/lib/oxidized/model/zynos.rb +3 -3
- data/lib/oxidized/node.rb +44 -27
- data/lib/oxidized/nodes.rb +8 -4
- data/lib/oxidized/output/file.rb +28 -0
- data/lib/oxidized/output/git.rb +148 -41
- data/lib/oxidized/output/gitcrypt.rb +18 -13
- data/lib/oxidized/output/http.rb +5 -4
- data/lib/oxidized/output/output.rb +14 -0
- data/lib/oxidized/source/http.rb +4 -2
- data/lib/oxidized/version.rb +6 -4
- data/lib/oxidized/worker.rb +13 -13
- data/lib/oxidized.rb +3 -24
- data/lib/refinements.rb +2 -0
- data/oxidized.gemspec +10 -8
- metadata +74 -41
- data/examples/podman-compose/Makefile +0 -103
- data/examples/podman-compose/README.md +0 -94
- data/examples/podman-compose/docker-compose.yml +0 -30
- data/examples/podman-compose/gitserver/.gitignore +0 -1
- data/examples/podman-compose/gitserver/Dockerfile +0 -14
- data/examples/podman-compose/model-simulation/Dockerfile-model +0 -13
- data/examples/podman-compose/model-simulation/asternos.sh +0 -36
- data/examples/podman-compose/oxidized-config/.gitignore +0 -10
- data/examples/podman-compose/oxidized-config/config +0 -46
- data/examples/podman-compose/oxidized-config/config_csv-file +0 -46
- data/examples/podman-compose/oxidized-config/config_csv-gitserver +0 -56
- data/examples/podman-compose/oxidized-config/router.db +0 -1
- data/examples/podman-compose/oxidized-ssh/.gitignore +0 -1
- data/examples/podman-compose/oxidized-ssh/README.md +0 -14
data/lib/oxidized/node.rb
CHANGED
@@ -6,15 +6,18 @@ module Oxidized
|
|
6
6
|
class ModelNotFound < OxidizedError; end
|
7
7
|
|
8
8
|
class Node
|
9
|
+
include SemanticLogger::Loggable
|
10
|
+
|
9
11
|
attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last, :repo
|
10
12
|
attr_accessor :running, :user, :email, :msg, :from, :stats, :retry, :err_type, :err_reason
|
11
13
|
alias running? running
|
12
14
|
|
15
|
+
# opt is a hash with the node parameters given in the source (:name, :group, :ip...)
|
13
16
|
def initialize(opt)
|
14
|
-
|
17
|
+
logger.debug 'resolving DNS for %s...' % opt[:name]
|
15
18
|
# remove the prefix if an IP Address is provided with one as IPAddr converts it to a network address.
|
16
19
|
ip_addr, = opt[:ip].to_s.split("/")
|
17
|
-
|
20
|
+
logger.debug 'IPADDR %s' % ip_addr.to_s
|
18
21
|
@name = opt[:name]
|
19
22
|
@ip = IPAddr.new(ip_addr).to_s rescue nil
|
20
23
|
@ip ||= Resolv.new.getaddress(@name) if Oxidized.config.resolve_dns?
|
@@ -37,7 +40,8 @@ module Oxidized
|
|
37
40
|
end
|
38
41
|
|
39
42
|
def run
|
40
|
-
status
|
43
|
+
status = :fail
|
44
|
+
config = nil
|
41
45
|
@input.each do |input|
|
42
46
|
# don't try input if model is missing config block, we may need strong config to class_name map
|
43
47
|
cfg_name = input.to_s.split('::').last.downcase
|
@@ -45,14 +49,16 @@ module Oxidized
|
|
45
49
|
|
46
50
|
@model.input = input = input.new
|
47
51
|
if (config = run_input(input))
|
48
|
-
|
52
|
+
logger.debug "#{input.class.name} ran for #{name} successfully"
|
49
53
|
status = :success
|
50
54
|
break
|
51
55
|
else
|
52
|
-
|
56
|
+
logger.debug "#{input.class.name} failed for #{name}"
|
53
57
|
status = :no_connection
|
54
58
|
end
|
55
59
|
end
|
60
|
+
logger.error "No suitable input found for #{name}" unless @model.input
|
61
|
+
|
56
62
|
@model.input = nil
|
57
63
|
[status, config]
|
58
64
|
end
|
@@ -75,13 +81,18 @@ module Oxidized
|
|
75
81
|
level = rescue_fail[resc]
|
76
82
|
resc = " (rescued #{resc})"
|
77
83
|
end
|
78
|
-
|
84
|
+
logger.send(level, '%s raised %s%s with msg "%s"' % [ip, err.class, resc, err.message])
|
79
85
|
@err_type = err.class.to_s
|
80
86
|
@err_reason = err.message.to_s
|
81
87
|
false
|
82
88
|
rescue StandardError => e
|
83
89
|
# Send a message in debug mode in case we are not able to create a crashfile
|
84
|
-
|
90
|
+
logger.error "#{ip} raised #{e.class} with msg #{e.message}, creating crashfile"
|
91
|
+
unless Oxidized.config.crash.directory?
|
92
|
+
logger.error "Cannot create crashfile for exception", e
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
85
96
|
crashdir = Oxidized.config.crash.directory
|
86
97
|
crashfile = Oxidized.config.crash.hostnames? ? name : ip.to_s
|
87
98
|
FileUtils.mkdir_p(crashdir) unless File.directory?(crashdir)
|
@@ -92,7 +103,7 @@ module Oxidized
|
|
92
103
|
fh.puts '-' * 50
|
93
104
|
fh.puts e.backtrace
|
94
105
|
end
|
95
|
-
|
106
|
+
logger.error '%s raised %s with msg "%s", %s saved' % [ip, e.class, e.message, crashfile]
|
96
107
|
@err_type = e.class.to_s
|
97
108
|
@err_reason = e.message.to_s
|
98
109
|
false
|
@@ -122,14 +133,10 @@ module Oxidized
|
|
122
133
|
h
|
123
134
|
end
|
124
135
|
|
136
|
+
JobStruct = Struct.new(:start, :end, :status, :time)
|
125
137
|
def last=(job)
|
126
138
|
if job
|
127
|
-
|
128
|
-
ostruct.start = job.start
|
129
|
-
ostruct.end = job.end
|
130
|
-
ostruct.status = job.status
|
131
|
-
ostruct.time = job.time
|
132
|
-
@last = ostruct
|
139
|
+
@last = JobStruct.new(job.start, job.end, job.status, job.time)
|
133
140
|
else
|
134
141
|
@last = nil
|
135
142
|
end
|
@@ -160,8 +167,11 @@ module Oxidized
|
|
160
167
|
|
161
168
|
def resolve_input(opt)
|
162
169
|
inputs = resolve_key :input, opt, Oxidized.config.input.default
|
163
|
-
inputs.split(
|
164
|
-
|
170
|
+
inputs.split(',').map do |input|
|
171
|
+
input.strip!
|
172
|
+
unless Oxidized.mgr.input[input]
|
173
|
+
Oxidized.mgr.add_input(input) || raise(MethodNotFound, "#{input} not found for node #{ip}")
|
174
|
+
end
|
165
175
|
|
166
176
|
Oxidized.mgr.input[input]
|
167
177
|
end
|
@@ -169,7 +179,10 @@ module Oxidized
|
|
169
179
|
|
170
180
|
def resolve_output(opt)
|
171
181
|
output = resolve_key :output, opt, Oxidized.config.output.default
|
172
|
-
|
182
|
+
unless Oxidized.mgr.output[output]
|
183
|
+
Oxidized.mgr.add_output(output) || raise(MethodNotFound,
|
184
|
+
"#{output} not found for node #{ip}")
|
185
|
+
end
|
173
186
|
|
174
187
|
Oxidized.mgr.output[output]
|
175
188
|
end
|
@@ -177,7 +190,7 @@ module Oxidized
|
|
177
190
|
def resolve_model(opt)
|
178
191
|
model = resolve_key :model, opt
|
179
192
|
unless Oxidized.mgr.model[model]
|
180
|
-
|
193
|
+
logger.debug "Loading model #{model.inspect}"
|
181
194
|
Oxidized.mgr.add_model(model) || raise(ModelNotFound, "#{model} not found for node #{ip}")
|
182
195
|
end
|
183
196
|
Oxidized.mgr.model[model].new
|
@@ -200,42 +213,46 @@ module Oxidized
|
|
200
213
|
end
|
201
214
|
|
202
215
|
def resolve_key(key, opt, global = nil)
|
203
|
-
# resolve key: the priority is as follows:
|
216
|
+
# resolve key: the priority is as follows:
|
217
|
+
# node -> group specific model -> group -> model -> global passed -> global
|
204
218
|
# where node has the highest priority (= if defined, overwrites other values)
|
205
219
|
key_sym = key.to_sym
|
206
220
|
key_str = key.to_s
|
207
221
|
model_name = @model.class.name.to_s.downcase
|
208
|
-
|
222
|
+
logger.debug "resolving node key '#{key}', with passed global value of '#{global}' " \
|
223
|
+
"and node value '#{opt[key_sym]}'"
|
209
224
|
|
210
225
|
# Node
|
211
226
|
if opt[key_sym]
|
212
227
|
value = opt[key_sym]
|
213
|
-
|
228
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from node"
|
214
229
|
|
215
230
|
# Group specific model
|
216
|
-
elsif Oxidized.config.groups.has_key?(@group) &&
|
231
|
+
elsif Oxidized.config.groups.has_key?(@group) &&
|
232
|
+
Oxidized.config.groups[@group].models.has_key?(model_name) &&
|
233
|
+
Oxidized.config.groups[@group].models[model_name].has_key?(key_str)
|
217
234
|
value = Oxidized.config.groups[@group].models[model_name][key_str]
|
218
|
-
|
235
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from model in group"
|
219
236
|
|
220
237
|
# Group
|
221
238
|
elsif Oxidized.config.groups.has_key?(@group) && Oxidized.config.groups[@group].has_key?(key_str)
|
222
239
|
value = Oxidized.config.groups[@group][key_str]
|
223
|
-
|
240
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from group"
|
224
241
|
|
225
242
|
# Model
|
226
243
|
elsif Oxidized.config.models.has_key?(model_name) && Oxidized.config.models[model_name].has_key?(key_str)
|
227
244
|
value = Oxidized.config.models[model_name][key_str]
|
228
|
-
|
245
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from model"
|
229
246
|
|
230
247
|
# Global passed
|
231
248
|
elsif global
|
232
249
|
value = global
|
233
|
-
|
250
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from passed global value"
|
234
251
|
|
235
252
|
# Global
|
236
253
|
elsif Oxidized.config.has_key?(key_str)
|
237
254
|
value = Oxidized.config[key_str]
|
238
|
-
|
255
|
+
logger.debug "setting node key '#{key}' to value '#{value}' from global"
|
239
256
|
end
|
240
257
|
value
|
241
258
|
end
|
data/lib/oxidized/nodes.rb
CHANGED
@@ -5,6 +5,8 @@ module Oxidized
|
|
5
5
|
class NodeNotFound < OxidizedError; end
|
6
6
|
|
7
7
|
class Nodes < Array
|
8
|
+
include SemanticLogger::Loggable
|
9
|
+
|
8
10
|
attr_accessor :source, :jobs
|
9
11
|
alias put unshift
|
10
12
|
def load(node_want = nil)
|
@@ -12,7 +14,7 @@ module Oxidized
|
|
12
14
|
new = []
|
13
15
|
@source = Oxidized.config.source.default
|
14
16
|
Oxidized.mgr.add_source(@source) || raise(MethodNotFound, "cannot load node source '#{@source}', not found")
|
15
|
-
|
17
|
+
logger.info "Loading nodes"
|
16
18
|
nodes = Oxidized.mgr.source[@source].new.load node_want
|
17
19
|
nodes.each do |node|
|
18
20
|
# we want to load specific node(s), not all of them
|
@@ -22,13 +24,14 @@ module Oxidized
|
|
22
24
|
node_obj = Node.new node
|
23
25
|
new.push node_obj
|
24
26
|
rescue ModelNotFound => e
|
25
|
-
|
27
|
+
logger.error "node %s raised %s with message '%s'" % [node, e.class, e.message]
|
26
28
|
rescue Resolv::ResolvError => e
|
27
|
-
|
29
|
+
logger.error "node %s is not resolvable, raised %s with message '%s'" % [node, e.class, e.message]
|
28
30
|
end
|
29
31
|
end
|
30
32
|
size.zero? ? replace(new) : update_nodes(new)
|
31
|
-
|
33
|
+
Output.clean_obsolete_nodes(self) if node_want.nil?
|
34
|
+
logger.info "Loaded #{size} nodes"
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -74,6 +77,7 @@ module Oxidized
|
|
74
77
|
def next(node, opt = {})
|
75
78
|
return if running.find_index(node)
|
76
79
|
|
80
|
+
logger.info "Add node #{node} to running jobs"
|
77
81
|
with_lock do
|
78
82
|
n = del node
|
79
83
|
n.user = opt['user']
|
data/lib/oxidized/output/file.rb
CHANGED
@@ -19,6 +19,9 @@ module Oxidized
|
|
19
19
|
raise NoConfig, "no output file config, edit #{Oxidized::Config.configfile}"
|
20
20
|
end
|
21
21
|
|
22
|
+
# node: node name (String)
|
23
|
+
# outputs: Oxidized::Models::Outputs
|
24
|
+
# opts: hash of node vars
|
22
25
|
def store(node, outputs, opt = {})
|
23
26
|
file = ::File.expand_path @cfg.directory
|
24
27
|
file = ::File.join ::File.dirname(file), opt[:group] if opt[:group]
|
@@ -53,6 +56,31 @@ module Oxidized
|
|
53
56
|
def get_version(_node, _group, _oid)
|
54
57
|
'not supported'
|
55
58
|
end
|
59
|
+
|
60
|
+
def self.node_path(node_name, group_name = nil)
|
61
|
+
cfg_dir = ::File.expand_path Oxidized.config.output.file.directory
|
62
|
+
|
63
|
+
if group_name
|
64
|
+
::File.join ::File.dirname(cfg_dir), group_name, node_name
|
65
|
+
else
|
66
|
+
::File.join cfg_dir, node_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.clean_obsolete_nodes(active_nodes)
|
71
|
+
cfg_dir = ::File.expand_path Oxidized.config.output.file.directory
|
72
|
+
dir_base = ::File.dirname(cfg_dir)
|
73
|
+
default_dir = ::File.basename(cfg_dir)
|
74
|
+
|
75
|
+
keep_files = active_nodes.map { |n| node_path(n.name, n.group) }
|
76
|
+
active_groups = active_nodes.map(&:group).compact.uniq
|
77
|
+
|
78
|
+
[default_dir, *active_groups].each do |group|
|
79
|
+
Dir.glob(::File.join(dir_base, group, "*")).each do |file|
|
80
|
+
::File.delete(file) if ::File.file?(file) && !keep_files.include?(file)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
56
84
|
end
|
57
85
|
end
|
58
86
|
end
|
data/lib/oxidized/output/git.rb
CHANGED
@@ -21,17 +21,17 @@ module Oxidized
|
|
21
21
|
if @cfg.empty?
|
22
22
|
Oxidized.asetus.user.output.git.user = 'Oxidized'
|
23
23
|
Oxidized.asetus.user.output.git.email = 'o@example.com'
|
24
|
-
Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git')
|
24
|
+
Oxidized.asetus.user.output.git.repo = ::File.join(Config::ROOT, 'oxidized.git')
|
25
25
|
Oxidized.asetus.save :user
|
26
26
|
raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}"
|
27
27
|
end
|
28
28
|
|
29
29
|
if @cfg.repo.respond_to?(:each)
|
30
30
|
@cfg.repo.each do |group, repo|
|
31
|
-
@cfg.repo["#{group}="] = File.expand_path repo
|
31
|
+
@cfg.repo["#{group}="] = ::File.expand_path repo
|
32
32
|
end
|
33
33
|
else
|
34
|
-
@cfg.repo = File.expand_path @cfg.repo
|
34
|
+
@cfg.repo = ::File.expand_path @cfg.repo
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -45,7 +45,7 @@ module Oxidized
|
|
45
45
|
|
46
46
|
outputs.types.each do |type|
|
47
47
|
type_cfg = ''
|
48
|
-
type_repo = File.join(File.dirname(repo), type + '.git')
|
48
|
+
type_repo = ::File.join(::File.dirname(repo), type + '.git')
|
49
49
|
outputs.type(type).each do |output|
|
50
50
|
(type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
|
51
51
|
type_file = file + '--' + output.name
|
@@ -64,8 +64,8 @@ module Oxidized
|
|
64
64
|
# Returns the configuration of group/node_name
|
65
65
|
#
|
66
66
|
# #fetch is called by Nodes#fetch
|
67
|
-
# Nodes#fetch creates a new Output object each time, so
|
68
|
-
#
|
67
|
+
# Nodes#fetch creates a new Output object each time, so it not easy
|
68
|
+
# to cache the repo index in memory. But as we keep the repo index up
|
69
69
|
# to date on disk in #update_repo, we can read it from disk instead of
|
70
70
|
# rebuilding it each time.
|
71
71
|
def fetch(node, group)
|
@@ -79,29 +79,13 @@ module Oxidized
|
|
79
79
|
'node not found'
|
80
80
|
end
|
81
81
|
|
82
|
-
# give a hash of all oid
|
82
|
+
# give a hash of all oid revisions for the given node, and the date of
|
83
|
+
# the commit.
|
84
|
+
#
|
85
|
+
# Called by Nodes#version
|
83
86
|
def version(node, group)
|
84
|
-
|
85
|
-
|
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
|
102
|
-
end
|
103
|
-
walker.reset
|
104
|
-
tab
|
87
|
+
repo_path, node_path = yield_repo_and_path(node, group)
|
88
|
+
self.class.hash_list(node_path, repo_path)
|
105
89
|
rescue StandardError
|
106
90
|
'node not found'
|
107
91
|
end
|
@@ -143,10 +127,139 @@ module Oxidized
|
|
143
127
|
'no diffs'
|
144
128
|
end
|
145
129
|
|
130
|
+
# Return the list of oids for node_path in the repository repo_path
|
131
|
+
def self.hash_list(node_path, repo_path)
|
132
|
+
update_cache(repo_path)
|
133
|
+
@gitcache[repo_path][:nodes][node_path] || []
|
134
|
+
end
|
135
|
+
|
136
|
+
# Update @gitcache, a class instance variable, ensuring persistence
|
137
|
+
# by saving the cache independently of object instances
|
138
|
+
def self.update_cache(repo_path)
|
139
|
+
# initialize our cache as a class instance variable
|
140
|
+
@gitcache ||= {}
|
141
|
+
# When single_repo == false, we have multiple repositories
|
142
|
+
unless @gitcache[repo_path]
|
143
|
+
@gitcache[repo_path] = {}
|
144
|
+
@gitcache[repo_path][:nodes] = {}
|
145
|
+
@gitcache[repo_path][:last_commit] = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
repo = Rugged::Repository.new repo_path
|
149
|
+
|
150
|
+
walker = Rugged::Walker.new(repo)
|
151
|
+
walker.sorting(Rugged::SORT_DATE)
|
152
|
+
walker.push(repo.head.target.oid)
|
153
|
+
|
154
|
+
# We store the commits into a temporary cache. It will be prepended
|
155
|
+
# to @gitcache to preserve the order of the commits.
|
156
|
+
cache = {}
|
157
|
+
walker.each do |commit|
|
158
|
+
if commit.oid == @gitcache[repo_path][:last_commit]
|
159
|
+
# we have reached the last cached commit, so we're done
|
160
|
+
break
|
161
|
+
end
|
162
|
+
|
163
|
+
commit.diff.each_delta do |delta|
|
164
|
+
next unless delta.added? || delta.modified?
|
165
|
+
|
166
|
+
hash = {}
|
167
|
+
# We keep :date for reverse compatibility on oxidized-web <= 0.15.1
|
168
|
+
hash[:date] = commit.time.to_s
|
169
|
+
# date as a Time instance for more flexibility in oxidized-web
|
170
|
+
hash[:time] = commit.time
|
171
|
+
hash[:oid] = commit.oid
|
172
|
+
hash[:author] = commit.author
|
173
|
+
hash[:message] = commit.message
|
174
|
+
|
175
|
+
filename = delta.new_file[:path]
|
176
|
+
if cache[filename]
|
177
|
+
cache[filename].append hash
|
178
|
+
else
|
179
|
+
cache[filename] = [hash]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
cache.each_pair do |filename, hashlist|
|
185
|
+
if @gitcache[repo_path][:nodes][filename]
|
186
|
+
# using the splat operator (*) should be OK as hashlist should
|
187
|
+
# not be very big when working on deltas
|
188
|
+
@gitcache[repo_path][:nodes][filename].prepend(*hashlist)
|
189
|
+
else
|
190
|
+
@gitcache[repo_path][:nodes][filename] = hashlist
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Store the most recent commit
|
195
|
+
@gitcache[repo_path][:last_commit] = repo.head.target.oid
|
196
|
+
end
|
197
|
+
|
198
|
+
# Currently only used in unit tests
|
199
|
+
def self.clear_cache
|
200
|
+
@gitcache = nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.clean_obsolete_nodes(active_nodes)
|
204
|
+
git_config = Oxidized.config.output.git
|
205
|
+
repo_path = git_config.repo
|
206
|
+
|
207
|
+
unless git_config.single_repo?
|
208
|
+
logger.warn "clean_obsolete_nodes is not implemented for " \
|
209
|
+
"multiple git repositories"
|
210
|
+
return
|
211
|
+
end
|
212
|
+
|
213
|
+
if git_config.type_as_directory?
|
214
|
+
logger.warn "clean_obsolete_nodes is not implemented for output " \
|
215
|
+
"types as a directory within the git repository"
|
216
|
+
return
|
217
|
+
end
|
218
|
+
|
219
|
+
# The repo might not exist on the first run
|
220
|
+
return unless ::File.directory?(repo_path)
|
221
|
+
|
222
|
+
repo = Rugged::Repository.new repo_path
|
223
|
+
return if repo.empty?
|
224
|
+
|
225
|
+
keep_files = active_nodes.map do |n|
|
226
|
+
n.group ? ::File.join(n.group, n.name) : n.name
|
227
|
+
end
|
228
|
+
|
229
|
+
tree = repo.last_commit.tree
|
230
|
+
files_to_delete = []
|
231
|
+
|
232
|
+
tree.walk_blobs do |root, entry|
|
233
|
+
file_path = root.empty? ? entry[:name] : ::File.join(root, entry[:name])
|
234
|
+
files_to_delete << file_path unless keep_files.include?(file_path)
|
235
|
+
end
|
236
|
+
|
237
|
+
return if files_to_delete.empty?
|
238
|
+
|
239
|
+
logger.info "clean_obsolete_nodes: removing " \
|
240
|
+
"#{files_to_delete.size} obsolete configs"
|
241
|
+
index = repo.index
|
242
|
+
|
243
|
+
files_to_delete.each { |file_path| index.remove(file_path) }
|
244
|
+
|
245
|
+
repo.config['user.name'] = git_config.user
|
246
|
+
repo.config['user.email'] = git_config.email
|
247
|
+
Rugged::Commit.create(
|
248
|
+
repo,
|
249
|
+
tree: index.write_tree(repo),
|
250
|
+
message: "Removing #{files_to_delete.size} obsolete configs",
|
251
|
+
parents: [repo.head.target].compact,
|
252
|
+
update_ref: 'HEAD'
|
253
|
+
)
|
254
|
+
|
255
|
+
index.write
|
256
|
+
end
|
257
|
+
|
146
258
|
private
|
147
259
|
|
148
260
|
def yield_repo_and_path(node, group)
|
149
|
-
repo
|
261
|
+
repo = node.repo
|
262
|
+
path = node.name
|
150
263
|
|
151
264
|
path = "#{group}/#{node.name}" if group && !group.empty? && @cfg.single_repo?
|
152
265
|
|
@@ -158,10 +271,10 @@ module Oxidized
|
|
158
271
|
|
159
272
|
if @opt[:group]
|
160
273
|
if @cfg.single_repo?
|
161
|
-
file = File.join @opt[:group], file
|
274
|
+
file = ::File.join @opt[:group], file
|
162
275
|
else
|
163
276
|
repo = if repo.is_a?(::String)
|
164
|
-
File.join File.dirname(repo), @opt[:group] + '.git'
|
277
|
+
::File.join ::File.dirname(repo), @opt[:group] + '.git'
|
165
278
|
else
|
166
279
|
repo[@opt[:group]]
|
167
280
|
end
|
@@ -175,27 +288,21 @@ module Oxidized
|
|
175
288
|
begin
|
176
289
|
Rugged::Repository.init_at repo, :bare
|
177
290
|
rescue StandardError => create_error
|
178
|
-
raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}'
|
291
|
+
raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' " \
|
292
|
+
"was while trying to create git repo"
|
179
293
|
end
|
180
294
|
retry
|
181
295
|
end
|
182
296
|
end
|
183
297
|
|
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.
|
298
|
+
# Uploads data into file in the repository repo
|
192
299
|
#
|
193
300
|
# update_repo caches the index on disk. An index is usually used in a
|
194
301
|
# working directory and not in a bare repository, which confuses users.
|
195
302
|
# The alternative would be to rebuild the index each time, which a little
|
196
303
|
# time consuming. Caching the index in memory is difficult because a new
|
197
304
|
# Output object is created each time #store is called.
|
198
|
-
def update_repo(repo, file, data)
|
305
|
+
def update_repo(repo, file, data) # rubocop:disable Naming/PredicateMethod
|
199
306
|
oid_old = repo.blob_at(repo.head.target_id, file) rescue nil
|
200
307
|
return false if oid_old && (oid_old.content.b == data.b)
|
201
308
|
|
@@ -25,17 +25,17 @@ module Oxidized
|
|
25
25
|
if @cfg.empty?
|
26
26
|
Oxidized.asetus.user.output.gitcrypt.user = 'Oxidized'
|
27
27
|
Oxidized.asetus.user.output.gitcrypt.email = 'o@example.com'
|
28
|
-
Oxidized.asetus.user.output.gitcrypt.repo = File.join(Config::ROOT, 'oxidized.git')
|
28
|
+
Oxidized.asetus.user.output.gitcrypt.repo = ::File.join(Config::ROOT, 'oxidized.git')
|
29
29
|
Oxidized.asetus.save :user
|
30
30
|
raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}"
|
31
31
|
end
|
32
32
|
|
33
33
|
if @cfg.repo.respond_to?(:each)
|
34
34
|
@cfg.repo.each do |group, repo|
|
35
|
-
@cfg.repo["#{group}="] = File.expand_path repo
|
35
|
+
@cfg.repo["#{group}="] = ::File.expand_path repo
|
36
36
|
end
|
37
37
|
else
|
38
|
-
@cfg.repo = File.expand_path @cfg.repo
|
38
|
+
@cfg.repo = ::File.expand_path @cfg.repo
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -45,7 +45,7 @@ module Oxidized
|
|
45
45
|
@cfg.users.each do |user|
|
46
46
|
system("#{@gitcrypt_adduser} #{user}")
|
47
47
|
end
|
48
|
-
File.write(".gitattributes", "* filter=git-crypt diff=git-crypt\n.gitattributes !filter !diff")
|
48
|
+
::File.write(".gitattributes", "* filter=git-crypt diff=git-crypt\n.gitattributes !filter !diff")
|
49
49
|
repo.add(".gitattributes")
|
50
50
|
repo.commit("Initial commit: crypt all config files")
|
51
51
|
end
|
@@ -73,7 +73,7 @@ module Oxidized
|
|
73
73
|
|
74
74
|
outputs.types.each do |type|
|
75
75
|
type_cfg = ''
|
76
|
-
type_repo = File.join(File.dirname(repo), type + '.git')
|
76
|
+
type_repo = ::File.join(::File.dirname(repo), type + '.git')
|
77
77
|
outputs.type(type).each do |output|
|
78
78
|
(type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
|
79
79
|
type_file = file + '--' + output.name
|
@@ -95,9 +95,9 @@ module Oxidized
|
|
95
95
|
unlock repo
|
96
96
|
index = repo.index
|
97
97
|
# Empty repo ?
|
98
|
-
raise 'Empty git repo' if File.exist?(index.path)
|
98
|
+
raise 'Empty git repo' if ::File.exist?(index.path)
|
99
99
|
|
100
|
-
File.read path
|
100
|
+
::File.read path
|
101
101
|
lock repo
|
102
102
|
rescue StandardError
|
103
103
|
'node not found'
|
@@ -114,7 +114,10 @@ module Oxidized
|
|
114
114
|
tab = []
|
115
115
|
walker.each do |commit|
|
116
116
|
hash = {}
|
117
|
+
# We keep :date for reverse compatibility on oxidized-web <= 0.15.1
|
117
118
|
hash[:date] = commit.date.to_s
|
119
|
+
# date as a Time instance for more flexibility in oxidized-web
|
120
|
+
hash[:time] = commit.date
|
118
121
|
hash[:oid] = commit.objectish
|
119
122
|
hash[:author] = commit.author
|
120
123
|
hash[:message] = commit.message
|
@@ -173,7 +176,8 @@ module Oxidized
|
|
173
176
|
private
|
174
177
|
|
175
178
|
def yield_repo_and_path(node, group)
|
176
|
-
repo
|
179
|
+
repo = node.repo
|
180
|
+
path = node.name
|
177
181
|
|
178
182
|
path = "#{group}/#{node.name}" if group && @cfg.single_repo?
|
179
183
|
|
@@ -185,10 +189,10 @@ module Oxidized
|
|
185
189
|
|
186
190
|
if @opt[:group]
|
187
191
|
if @cfg.single_repo?
|
188
|
-
file = File.join @opt[:group], file
|
192
|
+
file = ::File.join @opt[:group], file
|
189
193
|
else
|
190
194
|
repo = if repo.is_a?(::String)
|
191
|
-
File.join File.dirname(repo), @opt[:group] + '.git'
|
195
|
+
::File.join ::File.dirname(repo), @opt[:group] + '.git'
|
192
196
|
else
|
193
197
|
repo[@opt[:group]]
|
194
198
|
end
|
@@ -198,12 +202,13 @@ module Oxidized
|
|
198
202
|
begin
|
199
203
|
update_repo repo, file, data, @msg, @user, @email
|
200
204
|
rescue Git::GitExecuteError, ArgumentError => e
|
201
|
-
|
205
|
+
logger.debug "open_error #{e} #{file}"
|
202
206
|
begin
|
203
207
|
grepo = Git.init repo
|
204
208
|
crypt_init grepo
|
205
209
|
rescue StandardError => create_error
|
206
|
-
raise GitCryptError, "first '#{e.message}' was raised while opening git repo, then
|
210
|
+
raise GitCryptError, "first '#{e.message}' was raised while opening git repo, then " \
|
211
|
+
"'#{create_error.message}' was while trying to create git repo"
|
207
212
|
end
|
208
213
|
retry
|
209
214
|
end
|
@@ -215,7 +220,7 @@ module Oxidized
|
|
215
220
|
grepo.config('user.email', email)
|
216
221
|
grepo.chdir do
|
217
222
|
unlock grepo
|
218
|
-
File.write(file, data)
|
223
|
+
::File.write(file, data)
|
219
224
|
grepo.add(file)
|
220
225
|
if grepo.status[file].nil? || !grepo.status[file].type.nil?
|
221
226
|
grepo.commit(msg)
|
data/lib/oxidized/output/http.rb
CHANGED
@@ -34,14 +34,14 @@ module Oxidized
|
|
34
34
|
|
35
35
|
case response.code.to_i
|
36
36
|
when 200 || 201
|
37
|
-
|
37
|
+
logger.info "Configuration http backup complete for #{node}"
|
38
38
|
p [:success]
|
39
39
|
when (400..499)
|
40
|
-
|
40
|
+
logger.info "Configuration http backup for #{node} failed status: #{response.body}"
|
41
41
|
p [:bad_request]
|
42
42
|
when (500..599)
|
43
43
|
p [:server_problems]
|
44
|
-
|
44
|
+
logger.info "Configuration http backup for #{node} failed status: #{response.body}"
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -55,7 +55,8 @@ module Oxidized
|
|
55
55
|
'group' => opt[:group],
|
56
56
|
'node' => node,
|
57
57
|
'config' => outputs.to_cfg
|
58
|
-
# actually we need to also iterate outputs, for other types like in gitlab.
|
58
|
+
# actually we need to also iterate outputs, for other types like in gitlab.
|
59
|
+
# But most people don't use 'type' functionality.
|
59
60
|
)
|
60
61
|
end
|
61
62
|
end
|