smart_proxy_abrt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README +4 -0
- data/Rakefile +25 -0
- data/bin/smart-proxy-abrt-send +92 -0
- data/bundler.d/abrt.rb +6 -0
- data/extra/foreman-proxy-abrt-send.cron +2 -0
- data/extra/rubygem-smart_proxy_abrt.spec +110 -0
- data/lib/smart_proxy_abrt.rb +3 -0
- data/lib/smart_proxy_abrt/abrt_api.rb +76 -0
- data/lib/smart_proxy_abrt/abrt_lib.rb +244 -0
- data/lib/smart_proxy_abrt/abrt_plugin.rb +12 -0
- data/lib/smart_proxy_abrt/abrt_version.rb +5 -0
- data/lib/smart_proxy_abrt/http_config.ru +5 -0
- data/settings.d/abrt.yml.example +26 -0
- data/test/abrt_api_test.rb +90 -0
- data/test/abrt_test.rb +125 -0
- data/test/fixtures/ureport-ondisk-host1-01 +1 -0
- data/test/fixtures/ureport-ondisk-host1-02 +1 -0
- data/test/fixtures/ureport-ondisk-host1-03 +1 -0
- data/test/fixtures/ureport-ondisk-host2-01 +1 -0
- data/test/fixtures/ureport1.json +45 -0
- data/test/test_helper.rb +10 -0
- metadata +67 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
# Test for 1.9
|
5
|
+
if (RUBY_VERSION.split('.').map{|s|s.to_i} <=> [1,9,0]) > 0 then
|
6
|
+
PLATFORM = RUBY_PLATFORM
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Default: run unit tests.'
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
desc 'Test the Foreman Proxy plugin.'
|
13
|
+
Rake::TestTask.new(:test) do |t|
|
14
|
+
t.libs << '.'
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.libs << 'test'
|
17
|
+
files = FileList['test/**/*_test.rb']
|
18
|
+
if PLATFORM =~ /mingw/
|
19
|
+
files = FileList['test/**/server_ms_test*']
|
20
|
+
else
|
21
|
+
files = FileList['test/**/*_test.rb'].delete_if{|f| f =~ /_ms_/}
|
22
|
+
end
|
23
|
+
t.test_files = files
|
24
|
+
t.verbose = true
|
25
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift '/usr/share/foreman-proxy/lib' #XXX!
|
4
|
+
$LOAD_PATH.unshift '/usr/share/foreman-proxy/modules' #XXX!
|
5
|
+
|
6
|
+
# We rely on the main smart_proxy module to initialize the plugins so that we
|
7
|
+
# can access our module's settings. Also used from smart-proxy code: global
|
8
|
+
# settings, logging, foreman requests.
|
9
|
+
require 'smart_proxy'
|
10
|
+
|
11
|
+
require 'smart_proxy_abrt'
|
12
|
+
require 'smart_proxy_abrt/abrt_lib'
|
13
|
+
|
14
|
+
# Substitute our own logger so that we don't log to the main smart-proxy logfile.
|
15
|
+
module Proxy
|
16
|
+
module Log
|
17
|
+
@@logger = ::Logger.new(Proxy::Abrt::Plugin.settings.abrt_send_log_file, 6, 1024*1024*10)
|
18
|
+
@@logger.level = ::Logger.const_get(Proxy::SETTINGS.log_level.upcase)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
include Proxy::Log
|
22
|
+
|
23
|
+
# Don't run if ABRT plugin is disabled.
|
24
|
+
exit unless Proxy::Abrt::Plugin.settings.enabled == true
|
25
|
+
|
26
|
+
if !Proxy::SETTINGS.foreman_url
|
27
|
+
logger.error "Foreman URL not configured"
|
28
|
+
exit false
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'satyr' if Proxy::Abrt::Plugin.settings.aggregate_reports
|
33
|
+
rescue LoadError
|
34
|
+
logger.error "The satyr gem required for report aggregation was not found. "\
|
35
|
+
"You need to either install it or disable the aggregation."
|
36
|
+
end
|
37
|
+
|
38
|
+
def send_reports_from_spool
|
39
|
+
reports_by_host = {}
|
40
|
+
|
41
|
+
# load reports from disk
|
42
|
+
reports = Proxy::Abrt::HostReport.load_from_spool
|
43
|
+
|
44
|
+
# aggregate reports by host and by duplication hash if possible
|
45
|
+
reports.each do |report|
|
46
|
+
if reports_by_host.has_key? report.host
|
47
|
+
begin
|
48
|
+
reports_by_host[report.host].merge report
|
49
|
+
rescue StandardError => e
|
50
|
+
logger.error "Failed to merge #{report.files[0]} " \
|
51
|
+
"into #{reports_by_host[report.host].files}: #{e}"
|
52
|
+
end
|
53
|
+
else
|
54
|
+
reports_by_host[report.host] = report
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# send reports
|
59
|
+
reports_by_host.each_value do |hr|
|
60
|
+
begin
|
61
|
+
result = hr.send_to_foreman
|
62
|
+
rescue StandardError => e
|
63
|
+
logger.error "Unable to forward to Foreman server: #{e}"
|
64
|
+
next
|
65
|
+
end
|
66
|
+
unless result.is_a? Net::HTTPSuccess
|
67
|
+
logger.error "Foreman server rejected report (status #{result.code}): #{result.body}"
|
68
|
+
end
|
69
|
+
begin
|
70
|
+
hr.unlink
|
71
|
+
rescue StandardError => e
|
72
|
+
logger.error "Cannot delete #{hr.files}: #{e}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if ARGV[0] == "--daemon"
|
78
|
+
if PLATFORM =~ /mingw/
|
79
|
+
puts "Daemon mode is not supported on Windows."
|
80
|
+
exit false
|
81
|
+
end
|
82
|
+
require 'daemon'
|
83
|
+
Process.daemon true
|
84
|
+
|
85
|
+
sleep_interval = Proxy::Abrt::Plugin.settings.abrt_send_period || 600
|
86
|
+
loop do
|
87
|
+
send_reports_from_spool
|
88
|
+
sleep sleep_interval
|
89
|
+
end
|
90
|
+
else
|
91
|
+
send_reports_from_spool
|
92
|
+
end
|
data/bundler.d/abrt.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
%global gem_name smart_proxy_abrt
|
2
|
+
|
3
|
+
%global foreman_proxy_bundlerd_dir /usr/share/foreman-proxy/bundler.d
|
4
|
+
%global foreman_proxy_pluginconf_dir /etc/foreman-proxy/settings.d
|
5
|
+
%global spool_dir /var/spool/foreman-proxy-abrt
|
6
|
+
|
7
|
+
Name: rubygem-%{gem_name}
|
8
|
+
Version: 0.0.1
|
9
|
+
Release: 1%{?dist}
|
10
|
+
Summary: Automatic Bug Reporting Tool plugin for Foreman's smart proxy
|
11
|
+
Group: Applications/Internet
|
12
|
+
License: GPLv3
|
13
|
+
URL: http://github.com/abrt/smart-proxy-abrt
|
14
|
+
Source0: https://fedorahosted.org/released/abrt/%{gem_name}-%{version}.gem
|
15
|
+
Requires: ruby(release)
|
16
|
+
Requires: ruby(rubygems)
|
17
|
+
Requires: rubygem(ffi)
|
18
|
+
Requires: foreman-proxy >= 1.6.0
|
19
|
+
Requires: crontabs
|
20
|
+
## does not exist in repository yet
|
21
|
+
#Requires: rubygem-satyr
|
22
|
+
BuildRequires: ruby(release)
|
23
|
+
BuildRequires: rubygems-devel
|
24
|
+
BuildRequires: ruby
|
25
|
+
BuildRequires: rubygem(ffi)
|
26
|
+
BuildRequires: rubygem(minitest)
|
27
|
+
BuildArch: noarch
|
28
|
+
Provides: rubygem(%{gem_name}) = %{version}
|
29
|
+
|
30
|
+
%description
|
31
|
+
This smart proxy plugin, together with a Foreman plugin, add the capability to
|
32
|
+
send ABRT micro-reports from your managed hosts to Foreman.
|
33
|
+
|
34
|
+
%package doc
|
35
|
+
Summary: Documentation for %{name}
|
36
|
+
Group: Documentation
|
37
|
+
Requires:%{name} = %{version}-%{release}
|
38
|
+
|
39
|
+
%description doc
|
40
|
+
Documentation for %{name}
|
41
|
+
|
42
|
+
%prep
|
43
|
+
gem unpack %{SOURCE0}
|
44
|
+
%setup -q -D -T -n %{gem_name}-%{version}
|
45
|
+
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
|
46
|
+
|
47
|
+
%build
|
48
|
+
# Create the gem as gem install only works on a gem file
|
49
|
+
gem build %{gem_name}.gemspec
|
50
|
+
|
51
|
+
# %%gem_install compiles any C extensions and installs the gem into ./%gem_dir
|
52
|
+
# by default, so that we can move it into the buildroot in %%install
|
53
|
+
%gem_install
|
54
|
+
|
55
|
+
%install
|
56
|
+
# Packaging guidelines say: Do not ship tests
|
57
|
+
rm -r .%{gem_instdir}/test .%{gem_instdir}/Rakefile
|
58
|
+
rm .%{gem_instdir}/extra/*.spec
|
59
|
+
|
60
|
+
mkdir -p %{buildroot}%{gem_dir}
|
61
|
+
cp -a .%{gem_dir}/* \
|
62
|
+
%{buildroot}%{gem_dir}/
|
63
|
+
|
64
|
+
# executables
|
65
|
+
mkdir -p %{buildroot}%{_bindir}
|
66
|
+
cp -a .%{_bindir}/* \
|
67
|
+
%{buildroot}%{_bindir}
|
68
|
+
|
69
|
+
# bundler file
|
70
|
+
mkdir -p %{buildroot}%{foreman_proxy_bundlerd_dir}
|
71
|
+
mv %{buildroot}%{gem_instdir}/bundler.d/abrt.rb \
|
72
|
+
%{buildroot}%{foreman_proxy_bundlerd_dir}
|
73
|
+
|
74
|
+
# sample config
|
75
|
+
mkdir -p %{buildroot}%{foreman_proxy_pluginconf_dir}
|
76
|
+
mv %{buildroot}%{gem_instdir}/settings.d/abrt.yml.example \
|
77
|
+
%{buildroot}%{foreman_proxy_pluginconf_dir}/
|
78
|
+
|
79
|
+
# crontab
|
80
|
+
mkdir -p %{buildroot}%{_sysconfdir}/cron.d/
|
81
|
+
mv %{buildroot}%{gem_instdir}/extra/foreman-proxy-abrt-send.cron \
|
82
|
+
%{buildroot}%{_sysconfdir}/cron.d/%{name}
|
83
|
+
|
84
|
+
# create spool directory
|
85
|
+
mkdir -p %{buildroot}%{spool_dir}
|
86
|
+
|
87
|
+
#%check
|
88
|
+
#testrb -Ilib test
|
89
|
+
|
90
|
+
%files
|
91
|
+
%dir %{gem_instdir}
|
92
|
+
%{gem_libdir}
|
93
|
+
%exclude %{gem_cache}
|
94
|
+
%{gem_spec}
|
95
|
+
%{gem_instdir}/bin
|
96
|
+
|
97
|
+
%dir %attr(0755, foreman-proxy, foreman-proxy) %{spool_dir}
|
98
|
+
%{foreman_proxy_bundlerd_dir}/abrt.rb
|
99
|
+
%{_bindir}/smart-proxy-abrt-send
|
100
|
+
%doc %{foreman_proxy_pluginconf_dir}/abrt.yml.example
|
101
|
+
%config(noreplace) %{_sysconfdir}/cron.d/%{name}
|
102
|
+
|
103
|
+
%files doc
|
104
|
+
%{gem_docdir}
|
105
|
+
%{gem_instdir}/README
|
106
|
+
%{gem_instdir}/LICENSE
|
107
|
+
|
108
|
+
%changelog
|
109
|
+
* Tue Jul 15 2014 Martin Milata <mmilata@redhat.com> - 0.0.1-1
|
110
|
+
- Initial package
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'smart_proxy_abrt/abrt_lib'
|
5
|
+
|
6
|
+
STATUS_ACCEPTED = 202
|
7
|
+
|
8
|
+
module Proxy::Abrt
|
9
|
+
class Api < ::Sinatra::Base
|
10
|
+
include ::Proxy::Log
|
11
|
+
helpers ::Proxy::Helpers
|
12
|
+
|
13
|
+
post '/reports/new/' do
|
14
|
+
begin
|
15
|
+
cn = Proxy::Abrt::common_name request
|
16
|
+
rescue Proxy::Error::Unauthorized => e
|
17
|
+
log_halt 403, "Client authentication failed: #{e.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
ureport_json = request['file'][:tempfile].read
|
21
|
+
ureport = JSON.parse(ureport_json)
|
22
|
+
|
23
|
+
#forward to FAF
|
24
|
+
response = nil
|
25
|
+
if Proxy::Abrt::Plugin.settings.server_url
|
26
|
+
begin
|
27
|
+
result = Proxy::Abrt::faf_request "/reports/new/", ureport_json
|
28
|
+
response = result.body if result.code.to_s == STATUS_ACCEPTED.to_s
|
29
|
+
rescue StandardError => e
|
30
|
+
logger.error "Unable to forward to ABRT server: #{e}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
unless response
|
34
|
+
# forwarding is not configured or failed
|
35
|
+
# FAF source that generates replies is in src/webfaf/reports/views.py
|
36
|
+
response = { "result" => false,
|
37
|
+
"message" => "Report queued" }
|
38
|
+
if Proxy::SETTINGS.foreman_url
|
39
|
+
foreman_url = Proxy::SETTINGS.foreman_url
|
40
|
+
foreman_url += "/" if url[-1] != "/"
|
41
|
+
foreman_url += "hosts/#{cn}/abrt_reports"
|
42
|
+
response["reported_to"] = [{ "reporter" => "Foreman",
|
43
|
+
"type" => "url",
|
44
|
+
"value" => foreman_url }]
|
45
|
+
end
|
46
|
+
response = response.to_json
|
47
|
+
end
|
48
|
+
|
49
|
+
#save report to disk
|
50
|
+
begin
|
51
|
+
Proxy::Abrt::HostReport.save cn, ureport
|
52
|
+
rescue StandardError => e
|
53
|
+
log_halt 500, "Failed to save the report: #{e}"
|
54
|
+
end
|
55
|
+
|
56
|
+
status STATUS_ACCEPTED
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
post '/reports/:action/' do
|
61
|
+
# pass through to real FAF if configured
|
62
|
+
if Proxy::Abrt::Plugin.settings.server_url
|
63
|
+
body = request['file'][:tempfile].read
|
64
|
+
begin
|
65
|
+
result = Proxy::Abrt::faf_request "/reports/#{params[:action]}/", body
|
66
|
+
rescue StandardError => e
|
67
|
+
log_halt 503, "ABRT server unavailable: #{e}"
|
68
|
+
end
|
69
|
+
status result.code
|
70
|
+
result.body
|
71
|
+
else
|
72
|
+
log_halt 501, "foreman-proxy does not implement /reports/#{params[:action]}/"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'time'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
require 'proxy/log'
|
8
|
+
require 'proxy/request'
|
9
|
+
|
10
|
+
module Proxy::Abrt
|
11
|
+
def self.random_alpha_string(length)
|
12
|
+
base = ('a'..'z').to_a
|
13
|
+
result = ""
|
14
|
+
length.times { result << base[rand(base.size)] }
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generate multipart boundary separator
|
19
|
+
def self.suggest_separator
|
20
|
+
separator = "-"*28
|
21
|
+
separator + self.random_alpha_string(16)
|
22
|
+
end
|
23
|
+
|
24
|
+
# It seems that Net::HTTP does not support multipart/form-data - this function
|
25
|
+
# is adapted from http://stackoverflow.com/a/213276 and lib/proxy/request.rb
|
26
|
+
def self.form_data_file(content, file_content_type)
|
27
|
+
# Assemble the request body using the special multipart format
|
28
|
+
thepart = "Content-Disposition: form-data; name=\"file\"; filename=\"*buffer*\"\r\n" +
|
29
|
+
"Content-Type: #{ file_content_type }\r\n\r\n#{ content }\r\n"
|
30
|
+
|
31
|
+
boundary = self.suggest_separator
|
32
|
+
while thepart.include? boundary
|
33
|
+
boundary = self.suggest_separator
|
34
|
+
end
|
35
|
+
|
36
|
+
body = "--" + boundary + "\r\n" + thepart + "--" + boundary + "--\r\n"
|
37
|
+
headers = {
|
38
|
+
"User-Agent" => "foreman-proxy/#{Proxy::VERSION}",
|
39
|
+
"Content-Type" => "multipart/form-data; boundary=#{ boundary }",
|
40
|
+
"Content-Length" => body.length.to_s
|
41
|
+
}
|
42
|
+
|
43
|
+
return headers, body
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.faf_request(path, content, content_type="application/json")
|
47
|
+
uri = URI.parse(Proxy::Abrt::Plugin.settings.server_url.to_s)
|
48
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
49
|
+
http.use_ssl = uri.scheme == 'https'
|
50
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
51
|
+
|
52
|
+
if Proxy::Abrt::Plugin.settings.server_ssl_noverify
|
53
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
54
|
+
end
|
55
|
+
|
56
|
+
if Proxy::Abrt::Plugin.settings.server_ssl_cert && !Proxy::Abrt::Plugin.settings.server_ssl_cert.to_s.empty? \
|
57
|
+
&& Proxy::Abrt::Plugin.settings.server_ssl_key && !Proxy::Abrt::Plugin.settings.server_ssl_key.to_s.empty?
|
58
|
+
http.cert = OpenSSL::X509::Certificate.new(File.read(Proxy::Abrt::Plugin.settings.server_ssl_cert))
|
59
|
+
http.key = OpenSSL::PKey::RSA.new(File.read(Proxy::Abrt::Plugin.settings.server_ssl_key), nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
headers, body = self.form_data_file content, content_type
|
63
|
+
|
64
|
+
path = [uri.path, path].join unless uri.path.empty?
|
65
|
+
response = http.start { |con| con.post(path, body, headers) }
|
66
|
+
|
67
|
+
response
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.common_name(request)
|
71
|
+
client_cert = request.env['SSL_CLIENT_CERT']
|
72
|
+
raise Proxy::Error::Unauthorized, "Client certificate required" if client_cert.to_s.empty?
|
73
|
+
|
74
|
+
begin
|
75
|
+
client_cert = OpenSSL::X509::Certificate.new(client_cert)
|
76
|
+
rescue OpenSSL::OpenSSLError => e
|
77
|
+
raise Proxy::Error::Unauthorized, e.message
|
78
|
+
end
|
79
|
+
|
80
|
+
cn = client_cert.subject.to_a.detect { |name, value| name == 'CN' }
|
81
|
+
cn = cn[1] unless cn.nil?
|
82
|
+
raise Proxy::Error::Unauthorized, "Common Name not found in the certificate" unless cn
|
83
|
+
|
84
|
+
return cn
|
85
|
+
end
|
86
|
+
|
87
|
+
class HostReport
|
88
|
+
include Proxy::Log
|
89
|
+
|
90
|
+
class AggregatedReport
|
91
|
+
attr_accessor :report, :count, :hash, :reported_at
|
92
|
+
def initialize(report, count, hash, reported_at)
|
93
|
+
@report = report
|
94
|
+
@count = count
|
95
|
+
@hash = hash
|
96
|
+
@reported_at = Time.parse reported_at
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Error < RuntimeError; end
|
101
|
+
|
102
|
+
attr_reader :host, :reports, :files, :by_hash
|
103
|
+
|
104
|
+
def initialize(fname)
|
105
|
+
contents = IO.read(fname)
|
106
|
+
json = JSON.parse(contents)
|
107
|
+
|
108
|
+
report = json["report"]
|
109
|
+
hash = HostReport.duphash report
|
110
|
+
ar = AggregatedReport.new(json["report"], 1, hash, json["reported_at"])
|
111
|
+
@reports = [ar]
|
112
|
+
# index the array elements by duphash, if they have one
|
113
|
+
@by_hash = {}
|
114
|
+
@by_hash[hash] = ar unless hash.nil?
|
115
|
+
@files = [fname]
|
116
|
+
@host = json["host"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def merge(other)
|
120
|
+
raise HostReport::Error, "Host names do not match" unless @host == other.host
|
121
|
+
|
122
|
+
other.reports.each do |ar|
|
123
|
+
if !ar.hash.nil? && @by_hash.has_key?(ar.hash)
|
124
|
+
# we already have this report, just increment the counter
|
125
|
+
found_report = @by_hash[ar.hash]
|
126
|
+
found_report.count += ar.count
|
127
|
+
found_report.reported_at = [found_report.reported_at, ar.reported_at].min
|
128
|
+
else
|
129
|
+
# we either don't have this report or it has no hash
|
130
|
+
@reports << ar
|
131
|
+
@by_hash[ar.hash] = ar unless ar.hash.nil?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
@files += other.files
|
135
|
+
end
|
136
|
+
|
137
|
+
def send_to_foreman
|
138
|
+
foreman_report = create_foreman_report
|
139
|
+
logger.debug "Sending #{foreman_report}"
|
140
|
+
Proxy::HttpRequest::ForemanRequest.new.send_request("/api/abrt_reports", foreman_report.to_json)
|
141
|
+
end
|
142
|
+
|
143
|
+
def unlink
|
144
|
+
@files.each do |fname|
|
145
|
+
logger.debug "Deleting #{fname}"
|
146
|
+
File.unlink(fname)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.save(host, report, reported_at=nil)
|
151
|
+
# create the spool dir if it does not exist
|
152
|
+
FileUtils.mkdir_p HostReport.spooldir
|
153
|
+
|
154
|
+
reported_at ||= Time.now.utc
|
155
|
+
on_disk_report = { "host" => host, "report" => report , "reported_at" => reported_at.to_s }
|
156
|
+
|
157
|
+
# write report to temporary file
|
158
|
+
temp_fname = with_unique_filename "new-" do |temp_fname|
|
159
|
+
File.open temp_fname, File::WRONLY|File::CREAT|File::EXCL do |tmpfile|
|
160
|
+
tmpfile.write(on_disk_report.to_json)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# rename it
|
165
|
+
with_unique_filename ("ureport-" + DateTime.now.iso8601 + "-") do |final_fname|
|
166
|
+
File.link temp_fname, final_fname
|
167
|
+
File.unlink temp_fname
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.load_from_spool
|
172
|
+
reports = []
|
173
|
+
report_files = Dir[File.join(HostReport.spooldir, "ureport-*")]
|
174
|
+
report_files.each do |fname|
|
175
|
+
begin
|
176
|
+
reports << new(fname)
|
177
|
+
rescue StandardError => e
|
178
|
+
logger.error "Failed to parse report #{fname}: #{e}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
reports
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def format_reports
|
187
|
+
@reports.collect do |ar|
|
188
|
+
r = {
|
189
|
+
"count" => ar.count,
|
190
|
+
"reported_at" => ar.reported_at.utc.to_s,
|
191
|
+
"full" => ar.report
|
192
|
+
}
|
193
|
+
r["duphash"] = ar.hash unless ar.hash.nil?
|
194
|
+
r
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# http://projects.theforeman.org/projects/foreman/wiki/Json-report-format
|
199
|
+
# To be replaced once Foreman understands other report types than from Puppet.
|
200
|
+
def create_foreman_report
|
201
|
+
{ "abrt_report" => {
|
202
|
+
"host" => @host,
|
203
|
+
"reports" => format_reports
|
204
|
+
}
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.duphash(report)
|
209
|
+
return nil if !Proxy::Abrt::Plugin.settings.aggregate_reports
|
210
|
+
|
211
|
+
begin
|
212
|
+
satyr_report = Satyr::Report.new report.to_json
|
213
|
+
stacktrace = satyr_report.stacktrace
|
214
|
+
thread = stacktrace.find_crash_thread
|
215
|
+
thread.duphash
|
216
|
+
rescue StandardError => e
|
217
|
+
logger.error "Error computing duphash: #{e}"
|
218
|
+
nil
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.unique_filename(prefix)
|
223
|
+
File.join(HostReport.spooldir, prefix + Proxy::Abrt::random_alpha_string(8))
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.with_unique_filename(prefix)
|
227
|
+
filename = unique_filename prefix
|
228
|
+
tries_left = 5
|
229
|
+
begin
|
230
|
+
yield filename
|
231
|
+
rescue Errno::EEXIST => e
|
232
|
+
filename = unique_filename prefix
|
233
|
+
tries_left -= 1
|
234
|
+
retry if tries_left > 0
|
235
|
+
raise HostReport::Error, "Unable to create unique file"
|
236
|
+
end
|
237
|
+
filename
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.spooldir
|
241
|
+
Proxy::Abrt::Plugin.settings.spooldir || File.join(APP_ROOT, "spool/foreman-proxy-abrt")
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|