/
report_importer.rb
170 lines (140 loc) · 5.16 KB
/
report_importer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
class ReportImporter
include Foreman::TelemetryHelper
delegate :logger, :to => :Rails
attr_reader :report, :report_scanners
# When writing your own Report importer, provide feature(s) of authorized Smart Proxies
def self.authorized_smart_proxy_features
@authorized_smart_proxy_features ||= []
end
def self.register_smart_proxy_feature(feature)
@authorized_smart_proxy_features = (authorized_smart_proxy_features + [feature]).uniq
end
def self.unregister_smart_proxy_feature(feature)
@authorized_smart_proxy_features -= [feature]
end
def self.import(raw, proxy_id = nil)
importer = new(raw, proxy_id)
importer.import
importer.report
end
# to be overriden in children
def report_name_class
raise NotImplementedError, "#{__method__} not implemented for this report importer"
end
def initialize(raw, proxy_id = nil)
raise ::Foreman::Exception.new(_('Invalid report')) unless raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)
@raw = raw
@proxy_id = proxy_id
end
def import
logger.debug { "Processing report: #{raw.inspect}" }
telemetry = {}
telemetry_duration_histogram(:report_importer_create, :ms, {type: self.class.name}, telemetry) do
create_report_and_logs
end
if report.persisted?
telemetry_duration_histogram(:report_importer_refresh, :ms, {type: self.class.name}, telemetry) do
host.refresh_statuses(statuses_for_refresh)
end
create = telemetry[:report_importer_create].try(:round, 1)
refresh = telemetry[:report_importer_refresh].try(:round, 1)
logger.info("Imported report for #{name} in #{create} ms, status refreshed in #{refresh} ms")
end
end
def add_reporter_specific_data
logger.info "Scanning report with: #{report_scanners.join(', ')}"
report_scanners.each do |scanner|
if (origin = scanner.identify_origin(raw))
report.origin = origin
scanner.add_reporter_data(report, raw)
break
end
end
logger.debug { "Changes after reporter specific data added: #{report.changes.inspect}" }
end
private
attr_reader :raw, :proxy_id
def name
@name ||= raw['host']
end
def host
hostname = name.downcase
@host ||= Host::Base.find_by_certname(hostname) ||
Host::Base.find_by_name(hostname) ||
Host::Managed.new(:name => hostname)
end
def time
@time ||= Time.parse(raw['reported_at']).utc
end
def logs
raw['logs'] || []
end
def import_log_messages
logs.each do |log|
# Parse the API format
level = log['log']['level']
msg = log['log']['messages']['message']
src = log['log']['sources']['source']
message = Message.find_or_create_by(value: msg)
source = Source.find_or_create_by(value: src)
# Symbols get turned into strings via the JSON API, so convert back here if it matches
# and expected log level. Log objects can't be created without one, so raise if not
raise(::Foreman::Exception.new(N_("Invalid log level: %s", level))) unless Report::LOG_LEVELS.include?(level)
Log.create(:message_id => message.id, :source_id => source.id, :report => report, :level => level.to_sym)
end
end
def report_status
raise NotImplementedError
end
def statuses_for_refresh
HostStatus.status_registry
end
def notify_on_report_error(mail_error_state)
if report.error?
# found a report with errors
# notify via email IF enabled is set to true
if host.disabled?
logger.warn "#{name} is disabled - skipping alert"
return
end
owners = host.owner.present? ? host.owner.recipients_for(:config_error_state) : []
users = ConfigManagementError.all_hosts.flat_map(&:users)
users = users.select do |user|
User.as user do
Host.authorized_as(user, :view_hosts).find(host.id).present?
rescue ActiveRecord::RecordNotFound
nil
end
end
owners.concat users
if owners.present?
logger.debug { "sending alert to #{owners.map(&:login).join(',')}" }
MailNotification[mail_error_state].deliver(report, :users => owners.uniq)
else
logger.debug { "no owner or recipients for alert on #{name}" }
end
end
end
def create_report_and_logs
if host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
logger.info("skipping report for #{name} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
@report = report_name_class.new
return @report
end
# we save the host without validation for two reasons:
# 1. It might be auto imported, therefore might not be valid (e.g. missing partition table etc)
# 2. We want this to be fast and light on the db.
# at this point, the report is important, not the host
host.save(:validate => false)
status = report_status
# and save our report
@report = report_name_class.new(:host => host, :reported_at => time, :status => status, :metrics => raw['metrics'])
# Run report scanner
add_reporter_specific_data
@report.save
@report
end
def report_scanners
Foreman::Plugin.report_scanner_registry.report_scanners
end
end