smart_proxy_reports 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -11
- data/lib/smart_proxy_reports/ansible_processor.rb +139 -36
- data/lib/smart_proxy_reports/friendly_message.rb +16 -0
- data/lib/smart_proxy_reports/processor.rb +19 -6
- data/lib/smart_proxy_reports/puppet_processor.rb +73 -44
- data/lib/smart_proxy_reports/reports_api.rb +1 -0
- data/lib/smart_proxy_reports/spooled_http_client.rb +22 -5
- data/lib/smart_proxy_reports/version.rb +1 -1
- data/settings.d/reports.yml.example +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ada1a0027fc55bf9192b9f31e6547173c9e491184274f79dc29664dae8b54dc7
|
4
|
+
data.tar.gz: 15a2bd27cd02863d15b3a1352cfdf183e10e02226cb37d90777f735a14b5adce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3f70e46aa78232547ac0ef5016ba8777be07e1ee3484fd60cde4e3bcb11dcf7bade9ff3fe40615cb944b827a81e104a0331f42e0638c36d7c1ec8b0769309d9
|
7
|
+
data.tar.gz: 488cdacf8e8685b46cd1163f889f22903b27e815baee13d3a139eedc921bdc5a3f4c81fb510d3fe4135c65e1d07745c2458adc7e53d405ab71712a2946dfd6b9
|
data/README.md
CHANGED
@@ -26,15 +26,17 @@ Few words about setting up a dev setup.
|
|
26
26
|
Checkoud foreman-ansible-modules and build it via `make` command. Configure
|
27
27
|
Ansible collection path to the build directory:
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
```ini
|
30
|
+
[defaults]
|
31
|
+
collection_path = /home/lzap/work/foreman-ansible-modules/build
|
32
|
+
callback_whitelist = foreman
|
33
|
+
[callback_foreman]
|
34
|
+
report_type = proxy
|
35
|
+
proxy_url = http://localhost:8000/reports
|
36
|
+
verify_certs = 0
|
37
|
+
client_cert = /home/lzap/DummyX509/client-one.crt
|
38
|
+
client_key = /home/lzap/DummyX509/client-one.key
|
39
|
+
```
|
38
40
|
|
39
41
|
Configure Foreman Ansible callback with the correct Foreman URL:
|
40
42
|
|
@@ -46,7 +48,7 @@ Then call Ansible:
|
|
46
48
|
|
47
49
|
For testing, there are several example data. Before importing them into Foreman, make sure to have `localhost` smart proxy and also a host named `report.example.com`. It is possible to capture example data via `incoming_save_dir` setting. Name generated files correctly and put them into the `contrib/fixtures` directory. There is a utility to use fixtures for development and testing purposes:
|
48
50
|
|
49
|
-
```
|
51
|
+
```console
|
50
52
|
$ contrib/upload-fixture
|
51
53
|
Usage:
|
52
54
|
contrib/upload-fixture -h Display this help message
|
@@ -90,7 +92,7 @@ systemctl enable --now puppetserver
|
|
90
92
|
```
|
91
93
|
|
92
94
|
If you prefer to use HTTPS, set the different reporturl and configure the CA certificates according to the example below
|
93
|
-
```
|
95
|
+
```bash
|
94
96
|
# use HTTPS, without Katello the port is 8443, with Katello it's 9090
|
95
97
|
puppet config set reporturl https://$HOSTNAME:8443/reports/puppet
|
96
98
|
# install the Smart Proxy CA certificate to the Puppet's localcacert store
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative "friendly_message"
|
2
|
-
|
3
1
|
# frozen_string_literal: true
|
4
2
|
|
3
|
+
require_relative "friendly_message"
|
4
|
+
|
5
5
|
module Proxy::Reports
|
6
6
|
class AnsibleProcessor < Processor
|
7
|
-
KEYS_TO_COPY = %w[
|
7
|
+
KEYS_TO_COPY = %w[check_mode].freeze
|
8
8
|
|
9
9
|
def initialize(data, json_body: true)
|
10
10
|
super(data, json_body: json_body)
|
@@ -12,6 +12,9 @@ module Proxy::Reports
|
|
12
12
|
@data = JSON.parse(data)
|
13
13
|
end
|
14
14
|
@body = {}
|
15
|
+
@failure = 0
|
16
|
+
@change = 0
|
17
|
+
@nochange = 0
|
15
18
|
logger.debug "Processing report #{report_id}"
|
16
19
|
debug_payload("Input", @data)
|
17
20
|
end
|
@@ -20,36 +23,50 @@ module Proxy::Reports
|
|
20
23
|
@data["uuid"] || generated_report_id
|
21
24
|
end
|
22
25
|
|
26
|
+
def count_summary(result)
|
27
|
+
if result["result"]["changed"]
|
28
|
+
@change += 1
|
29
|
+
else
|
30
|
+
@nochange += 1
|
31
|
+
end
|
32
|
+
if result["failed"]
|
33
|
+
@failure += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
23
37
|
def process_results
|
24
38
|
@data["results"]&.each do |result|
|
25
39
|
raise("Report do not contain required 'results/result' element") unless result["result"]
|
26
40
|
raise("Report do not contain required 'results/task' element") unless result["task"]
|
27
|
-
process_facts(result)
|
28
41
|
process_level(result)
|
29
42
|
friendly_message = FriendlyMessage.new(result)
|
30
43
|
result["friendly_message"] = friendly_message.generate_message
|
31
44
|
process_keywords(result)
|
45
|
+
count_summary(result)
|
32
46
|
end
|
33
47
|
@data["results"]
|
34
48
|
rescue StandardError => e
|
35
|
-
|
49
|
+
log_error("Unable to parse results", e)
|
36
50
|
@data["results"]
|
37
51
|
end
|
38
52
|
|
39
53
|
def process
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
54
|
+
@body["format"] = "ansible"
|
55
|
+
@body["id"] = report_id
|
56
|
+
@body["host"] = hostname_from_config || @data["host"]
|
57
|
+
@body["proxy"] = Proxy::Reports::Plugin.settings.reported_proxy_hostname
|
58
|
+
@body["reported_at"] = @data["reported_at"]
|
59
|
+
@body["reported_at_proxy"] = now_utc
|
60
|
+
measure :process_results do
|
46
61
|
@body["results"] = process_results
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
62
|
+
end
|
63
|
+
@body["summary"] = build_summary
|
64
|
+
process_root_keywords
|
65
|
+
@body["keywords"] = keywords
|
66
|
+
@body["telemetry"] = telemetry
|
67
|
+
@body["errors"] = errors if errors?
|
68
|
+
KEYS_TO_COPY.each do |key|
|
69
|
+
@body[key] = @data[key]
|
53
70
|
end
|
54
71
|
end
|
55
72
|
|
@@ -62,36 +79,121 @@ module Proxy::Reports
|
|
62
79
|
format: "ansible",
|
63
80
|
version: 1,
|
64
81
|
host: @body["host"],
|
65
|
-
reported_at: @body["reported_at"],
|
66
|
-
statuses: process_statuses,
|
67
82
|
proxy: @body["proxy"],
|
68
|
-
|
83
|
+
change: @body["summary"]["foreman"]["change"],
|
84
|
+
nochange: @body["summary"]["foreman"]["nochange"],
|
85
|
+
failure: @body["summary"]["foreman"]["failure"],
|
69
86
|
keywords: @body["keywords"],
|
87
|
+
body: @body,
|
70
88
|
)
|
71
89
|
end
|
72
90
|
|
73
91
|
def spool_report
|
92
|
+
facts_hash = measure :build_facts do
|
93
|
+
build_facts
|
94
|
+
end
|
95
|
+
if facts_hash
|
96
|
+
debug_payload("Facts output", facts_hash)
|
97
|
+
payload = measure :format_facts do
|
98
|
+
facts_hash.to_json
|
99
|
+
end
|
100
|
+
SpooledHttpClient.instance.spool(:ansible_facts, payload)
|
101
|
+
end
|
102
|
+
|
74
103
|
report_hash = build_report
|
75
104
|
debug_payload("Output", report_hash)
|
76
105
|
payload = measure :format do
|
77
106
|
report_hash.to_json
|
78
107
|
end
|
79
|
-
SpooledHttpClient.instance.spool(
|
108
|
+
SpooledHttpClient.instance.spool(:report, payload)
|
109
|
+
end
|
110
|
+
|
111
|
+
def find_facts_task
|
112
|
+
@data["results"]&.each do |result|
|
113
|
+
if result["result"] && result["result"]["ansible_facts"]
|
114
|
+
return result["result"]["ansible_facts"]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_facts
|
121
|
+
facts = find_facts_task
|
122
|
+
return nil unless facts
|
123
|
+
{
|
124
|
+
"name" => hostname_from_config || @data["host"],
|
125
|
+
"facts" => {
|
126
|
+
"ansible_facts" => facts,
|
127
|
+
"_type" => "ansible",
|
128
|
+
"_timestamp" => @data["reported_at"],
|
129
|
+
},
|
130
|
+
}
|
80
131
|
end
|
81
132
|
|
82
133
|
private
|
83
134
|
|
84
|
-
|
85
|
-
|
86
|
-
|
135
|
+
# foreman-ansible-modules 3.0 does not contain summary field, convert it here
|
136
|
+
# https://github.com/theforeman/foreman-ansible-modules/pull/1325/files
|
137
|
+
def build_summary
|
138
|
+
if @data["summary"]
|
139
|
+
native = @data["summary"]
|
140
|
+
elsif (status = @data["status"])
|
141
|
+
native = {
|
142
|
+
"changed" => status["applied"] || 0,
|
143
|
+
"failures" => status["failed"] || 0,
|
144
|
+
"ignored" => 0,
|
145
|
+
"ok" => 0,
|
146
|
+
"rescued" => 0,
|
147
|
+
"skipped" => status["skipped"] || 0,
|
148
|
+
"unreachable" => 0,
|
149
|
+
}
|
150
|
+
else
|
151
|
+
native = {}
|
152
|
+
end
|
153
|
+
{
|
154
|
+
"foreman" => {
|
155
|
+
"change" => @change, "nochange" => @nochange, "failure" => @failure,
|
156
|
+
},
|
157
|
+
"native" => native,
|
158
|
+
}
|
159
|
+
rescue StandardError => e
|
160
|
+
log_error("Unable to build summary", e)
|
161
|
+
{
|
162
|
+
"foreman" => {
|
163
|
+
"change" => @change, "nochange" => @nochange, "failure" => @failure,
|
164
|
+
},
|
165
|
+
"native" => {},
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def process_root_keywords
|
170
|
+
if (summary = @body["summary"])
|
171
|
+
if summary["changed"] && summary["changed"] > 0
|
172
|
+
add_keywords("AnsibleChanged")
|
173
|
+
elsif summary["failures"] && summary["failures"] > 0
|
174
|
+
add_keywords("AnsibleFailures")
|
175
|
+
elsif summary["unreachable"] && summary["unreachable"] > 0
|
176
|
+
add_keywords("AnsibleUnreachable")
|
177
|
+
elsif summary["rescued"] && summary["rescued"] > 0
|
178
|
+
add_keywords("AnsibleRescued")
|
179
|
+
elsif summary["ignored"] && summary["ignored"] > 0
|
180
|
+
add_keywords("AnsibleIgnored")
|
181
|
+
elsif summary["skipped"] && summary["skipped"] > 0
|
182
|
+
add_keywords("AnsibleSkipped")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
rescue StandardError => e
|
186
|
+
log_error("Unable to parse root summary keywords", e)
|
87
187
|
end
|
88
188
|
|
89
189
|
def process_keywords(result)
|
90
190
|
if result["failed"]
|
91
|
-
add_keywords("
|
191
|
+
add_keywords("AnsibleFailure", "AnsibleFailure:#{result["task"]["action"]}")
|
92
192
|
elsif result["result"]["changed"]
|
93
|
-
add_keywords("
|
193
|
+
add_keywords("AnsibleChanged")
|
94
194
|
end
|
195
|
+
rescue StandardError => e
|
196
|
+
log_error("Unable to parse keywords", e)
|
95
197
|
end
|
96
198
|
|
97
199
|
def process_level(result)
|
@@ -102,18 +204,19 @@ module Proxy::Reports
|
|
102
204
|
else
|
103
205
|
result["level"] = "info"
|
104
206
|
end
|
207
|
+
rescue StandardError => e
|
208
|
+
log_error("Unable to parse log level", e)
|
209
|
+
result["level"] = "info"
|
105
210
|
end
|
211
|
+
end
|
106
212
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
rescue StandardError => e
|
115
|
-
logger.error "Unable to process statuses", e
|
116
|
-
{ "applied" => 0, "failed" => 0, "pending" => 0, "other" => 0 }
|
213
|
+
def search_for_facts2222222222222222(result)
|
214
|
+
if result.respond_to?(:key?) && result.key?(:ansible_facts)
|
215
|
+
result[:ansible_facts]
|
216
|
+
elsif result.respond_to?(:each)
|
217
|
+
r = nil
|
218
|
+
result.find { |*a| r = search_for_facts(a.last) }
|
219
|
+
r
|
117
220
|
end
|
118
221
|
end
|
119
222
|
end
|
@@ -13,8 +13,11 @@ class FriendlyMessage
|
|
13
13
|
|
14
14
|
case @task_tree["action"]
|
15
15
|
when "ansible.builtin.package", "package" then msg = package_message
|
16
|
+
when "ansible.builtin.known_hosts", "known_hosts" then msg = known_hosts_message
|
17
|
+
when "ansible.builtin.pip", "pip" then msg = pip_message
|
16
18
|
when "ansible.builtin.template", "template" then msg = template_message
|
17
19
|
when "ansible.builtin.service", "service" then msg = service_message
|
20
|
+
when "ansible.builtin.unarchive", "unarchive" then msg = unarchive_message
|
18
21
|
when "ansible.builtin.group", "group" then msg = group_message
|
19
22
|
when "ansible.builtin.user", "user" then msg = user_message
|
20
23
|
when "ansible.builtin.cron", "cron" then msg = cron_message
|
@@ -40,6 +43,15 @@ class FriendlyMessage
|
|
40
43
|
"Package(s) #{packages} are #{state}"
|
41
44
|
end
|
42
45
|
|
46
|
+
def known_hosts_message
|
47
|
+
"#{@module_args_tree["name"]} is #{@module_args_tree["state"]} in #{@module_args_tree["path"]}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def pip_message
|
51
|
+
packages = human_readable_array(@module_args_tree["name"]) || "contained in #{@module_args_tree["requirements"]}"
|
52
|
+
"Package(s) #{packages} are #{@module_args_tree["state"]}"
|
53
|
+
end
|
54
|
+
|
43
55
|
def template_message
|
44
56
|
"Render template #{@module_args_tree["_original_basename"]} to #{@result_tree["dest"]}"
|
45
57
|
end
|
@@ -48,6 +60,10 @@ class FriendlyMessage
|
|
48
60
|
"Service #{@result_tree["name"]} is #{@result_tree["state"]} and enabled: #{@result_tree["enabled"]}"
|
49
61
|
end
|
50
62
|
|
63
|
+
def unarchive_message
|
64
|
+
"Archive #{@module_args_tree["src"]} unpacked into #{@module_args_tree["dest"]}"
|
65
|
+
end
|
66
|
+
|
51
67
|
def group_message
|
52
68
|
"User group #{@result_tree["name"]} is #{@result_tree["state"]} with gid: #{@result_tree["gid"]}"
|
53
69
|
end
|
@@ -31,19 +31,26 @@ module Proxy::Reports
|
|
31
31
|
@hostname_from_config ||= Proxy::Reports::Plugin.settings.override_hostname
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
34
|
+
def now_utc
|
35
|
+
# make sure it contains TZ info: "2022-01-20 14:16:26 UTC"
|
36
|
+
Time.now.utc.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_report_root(format:, version:, host:, proxy:, change:, nochange:, failure:, body:, keywords:)
|
35
40
|
{
|
36
41
|
"host_report" => {
|
37
42
|
"format" => format,
|
38
43
|
"version" => version,
|
39
44
|
"host" => host,
|
40
|
-
"reported_at" =>
|
45
|
+
"reported_at" => now_utc,
|
41
46
|
"proxy" => proxy,
|
42
47
|
"body" => @json_body ? body.to_json : body,
|
43
48
|
"keywords" => keywords,
|
44
|
-
|
49
|
+
"change" => change,
|
50
|
+
"nochange" => nochange,
|
51
|
+
"failure" => failure,
|
52
|
+
},
|
45
53
|
}
|
46
|
-
# TODO add metric with total time
|
47
54
|
end
|
48
55
|
|
49
56
|
def debug_payload?
|
@@ -67,8 +74,14 @@ module Proxy::Reports
|
|
67
74
|
|
68
75
|
attr_reader :errors
|
69
76
|
|
70
|
-
def log_error(message)
|
71
|
-
|
77
|
+
def log_error(message, exception = nil)
|
78
|
+
msg = if exception
|
79
|
+
"#{message}: #{exception}"
|
80
|
+
else
|
81
|
+
message
|
82
|
+
end
|
83
|
+
logger.error msg, exception
|
84
|
+
@errors << msg.to_s
|
72
85
|
end
|
73
86
|
|
74
87
|
def errors?
|
@@ -34,37 +34,46 @@ module Proxy::Reports
|
|
34
34
|
end
|
35
35
|
logs
|
36
36
|
rescue StandardError => e
|
37
|
-
|
37
|
+
log_error("Unable to parse logs", e)
|
38
38
|
logs
|
39
39
|
end
|
40
40
|
|
41
|
+
def process_root_keywords
|
42
|
+
status = @data["status"]
|
43
|
+
if status == "changed"
|
44
|
+
add_keywords("PuppetStatusChanged")
|
45
|
+
elsif status == "unchanged"
|
46
|
+
add_keywords("PuppetStatusUnchanged")
|
47
|
+
elsif status == "failed"
|
48
|
+
add_keywords("PuppetStatusFailed")
|
49
|
+
end
|
50
|
+
if @data["noop"] == "true"
|
51
|
+
add_keywords("PuppetNoop")
|
52
|
+
end
|
53
|
+
if @data["noop_pending"] == "true"
|
54
|
+
add_keywords("PuppetNoopPending")
|
55
|
+
end
|
56
|
+
rescue StandardError => e
|
57
|
+
log_error("Unable to parse root keywords", e)
|
58
|
+
end
|
59
|
+
|
41
60
|
def process_resource_statuses
|
42
61
|
statuses = []
|
43
62
|
@data["resource_statuses"]&.each_pair do |key, value|
|
44
63
|
statuses << key
|
45
64
|
@evaluation_times << [key, value["evaluation_time"]]
|
46
|
-
|
47
|
-
add_keywords("
|
48
|
-
value["
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
add_keywords("PuppetHasChange") if value["changed"]
|
54
|
-
add_keywords("PuppetHasChange") if value["change_count"] && value["change_count"] > 0
|
55
|
-
# changes
|
56
|
-
add_keywords("PuppetIsOutOfSync") if value["out_of_sync"]
|
57
|
-
add_keywords("PuppetIsOutOfSync") if value["out_of_sync_count"] && value["out_of_sync_count"] > 0
|
58
|
-
# skips
|
59
|
-
add_keywords("PuppetHasSkips") if value["skipped"]
|
60
|
-
# corrective changes
|
61
|
-
add_keywords("PuppetHasCorrectiveChange") if value["corrective_change"]
|
62
|
-
# others
|
65
|
+
add_keywords("PuppetFailed:#{key}", "PuppetFailed") if value["failed"]
|
66
|
+
add_keywords("PuppetFailedToRestart:#{key}", "PuppetFailedToRestart") if value["failed_to_restart"]
|
67
|
+
add_keywords("PuppetCorrectiveChange") if value["corrective_change"]
|
68
|
+
add_keywords("PuppetSkipped") if value["skipped"]
|
69
|
+
add_keywords("PuppetRestarted") if value["restarted"]
|
70
|
+
add_keywords("PuppetScheduled") if value["scheduled"]
|
71
|
+
add_keywords("PuppetOutOfSync") if value["out_of_sync"]
|
63
72
|
add_keywords("PuppetEnvironment:#{@data["environment"]}") if @data["environment"]
|
64
73
|
end
|
65
74
|
statuses
|
66
75
|
rescue StandardError => e
|
67
|
-
|
76
|
+
log_error("Unable to parse resource_statuses", e)
|
68
77
|
statuses
|
69
78
|
end
|
70
79
|
|
@@ -77,27 +86,36 @@ module Proxy::Reports
|
|
77
86
|
end
|
78
87
|
@evaluation_times
|
79
88
|
rescue StandardError => e
|
80
|
-
|
89
|
+
log_error("Unable to parse evaluation_times", e)
|
81
90
|
[]
|
82
91
|
end
|
83
92
|
|
84
93
|
def process
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
@body["format"] = "puppet"
|
95
|
+
@body["id"] = report_id
|
96
|
+
@body["host"] = hostname_from_config || @data["host"]
|
97
|
+
@body["proxy"] = Proxy::Reports::Plugin.settings.reported_proxy_hostname
|
98
|
+
@body["reported_at"] = @data["time"]
|
99
|
+
@body["reported_at_proxy"] = now_utc
|
100
|
+
KEYS_TO_COPY.each do |key|
|
101
|
+
@body[key] = @data[key]
|
102
|
+
end
|
103
|
+
process_root_keywords
|
104
|
+
measure :process_logs do
|
94
105
|
@body["logs"] = process_logs
|
106
|
+
end
|
107
|
+
measure :process_resource_statuses do
|
95
108
|
@body["resource_statuses"] = process_resource_statuses
|
96
|
-
|
109
|
+
end
|
110
|
+
measure :process_summary do
|
111
|
+
@body["summary"] = process_summary
|
112
|
+
end
|
113
|
+
measure :process_evaluation_times do
|
97
114
|
@body["evaluation_times"] = process_evaluation_times
|
98
|
-
@body["telemetry"] = telemetry
|
99
|
-
@body["errors"] = errors if errors?
|
100
115
|
end
|
116
|
+
@body["telemetry"] = telemetry
|
117
|
+
@body["keywords"] = keywords
|
118
|
+
@body["errors"] = errors if errors?
|
101
119
|
end
|
102
120
|
|
103
121
|
def build_report
|
@@ -109,11 +127,12 @@ module Proxy::Reports
|
|
109
127
|
format: "puppet",
|
110
128
|
version: 1,
|
111
129
|
host: @body["host"],
|
112
|
-
reported_at: @body["reported_at"],
|
113
|
-
statuses: process_statuses,
|
114
130
|
proxy: @body["proxy"],
|
115
|
-
|
131
|
+
change: @body["summary"]["foreman"]["change"],
|
132
|
+
nochange: @body["summary"]["foreman"]["nochange"],
|
133
|
+
failure: @body["summary"]["foreman"]["failure"],
|
116
134
|
keywords: @body["keywords"],
|
135
|
+
body: @body,
|
117
136
|
)
|
118
137
|
end
|
119
138
|
|
@@ -123,22 +142,32 @@ module Proxy::Reports
|
|
123
142
|
payload = measure :format do
|
124
143
|
report_hash.to_json
|
125
144
|
end
|
126
|
-
SpooledHttpClient.instance.spool(
|
145
|
+
SpooledHttpClient.instance.spool(:report, payload)
|
127
146
|
end
|
128
147
|
|
129
148
|
private
|
130
149
|
|
131
|
-
def
|
132
|
-
|
150
|
+
def process_summary
|
151
|
+
events = @body["metrics"]["events"]["values"].collect { |s| [s[0], s[2]] }.to_h
|
152
|
+
resources = @body["metrics"]["resources"]["values"].collect { |s| [s[0], s[2]] }.to_h
|
133
153
|
{
|
134
|
-
"
|
135
|
-
|
136
|
-
|
137
|
-
|
154
|
+
"foreman" => {
|
155
|
+
"change" => events["success"],
|
156
|
+
"nochange" => resources["total"] - events["failure"] - events["success"],
|
157
|
+
"failure" => events["failure"],
|
158
|
+
},
|
159
|
+
"native" => resources,
|
138
160
|
}
|
139
161
|
rescue StandardError => e
|
140
|
-
|
141
|
-
{
|
162
|
+
log_error("Unable to parse summary counts", e)
|
163
|
+
{
|
164
|
+
"foreman" => {
|
165
|
+
"change" => 0,
|
166
|
+
"nochange" => 0,
|
167
|
+
"failure" => 0,
|
168
|
+
},
|
169
|
+
"native" => {},
|
170
|
+
}
|
142
171
|
end
|
143
172
|
end
|
144
173
|
end
|
@@ -47,6 +47,7 @@ module Proxy::Reports
|
|
47
47
|
log_halt(415, "Missing body") if input.empty?
|
48
48
|
json_body = to_bool(params[:json_body], true)
|
49
49
|
processor = Processor.new_processor(format, input, json_body: json_body)
|
50
|
+
status 202
|
50
51
|
processor.spool_report
|
51
52
|
rescue => e
|
52
53
|
log_halt 415, e, "Error during report processing: #{e.message}"
|
@@ -52,6 +52,14 @@ module Proxy::Reports
|
|
52
52
|
@worker.join
|
53
53
|
end
|
54
54
|
|
55
|
+
def get_endpoint(name)
|
56
|
+
if name&.end_with? 'ansible_facts'
|
57
|
+
"/api/v2/hosts/facts"
|
58
|
+
else
|
59
|
+
"/api/v2/host_reports"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
55
63
|
def process
|
56
64
|
processed = 0
|
57
65
|
client = ::Proxy::HttpRequest::ForemanRequest.new
|
@@ -59,11 +67,12 @@ module Proxy::Reports
|
|
59
67
|
# send all files via a single persistent HTTP connection
|
60
68
|
logger.debug "Opening HTTP connection to Foreman"
|
61
69
|
client.http.start do |http|
|
62
|
-
Dir.glob(spool_path("todo", "*")) do |filename|
|
70
|
+
Dir.glob(spool_path("todo", "*")).sort.each do |filename|
|
63
71
|
basename = File.basename(filename)
|
72
|
+
endpoint = get_endpoint(basename)
|
64
73
|
logger.debug "Sending report #{basename}"
|
65
74
|
begin
|
66
|
-
post = factory.create_post(
|
75
|
+
post = factory.create_post(endpoint, File.read(filename))
|
67
76
|
response = http.request(post)
|
68
77
|
logger.info "Report #{basename} sent with HTTP response #{response.code}"
|
69
78
|
logger.debug { "Response body: #{response.body}" }
|
@@ -74,7 +83,7 @@ module Proxy::Reports
|
|
74
83
|
FileUtils.rm_f spool_path("todo", basename)
|
75
84
|
end
|
76
85
|
else
|
77
|
-
logger.debug { "Moving failed report to 'fail'
|
86
|
+
logger.debug { "Moving failed report to 'fail' spool directory" }
|
78
87
|
spool_move("todo", "done", basename)
|
79
88
|
end
|
80
89
|
processed += 1
|
@@ -91,12 +100,20 @@ module Proxy::Reports
|
|
91
100
|
@worker.wakeup if @worker
|
92
101
|
end
|
93
102
|
|
94
|
-
def spool(
|
95
|
-
filename =
|
103
|
+
def spool(prefix, data)
|
104
|
+
filename = unique_filename + "-" + prefix.to_s
|
96
105
|
file = spool_path("temp", filename)
|
97
106
|
File.open(file, "w") { |f| f.write(data) }
|
98
107
|
spool_move("temp", "todo", filename)
|
99
108
|
wakeup
|
100
109
|
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Ensure that files are named so they can be sorted and processed in the same order
|
114
|
+
def unique_filename
|
115
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :second).to_s(36) +
|
116
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond).to_s(36)
|
117
|
+
end
|
101
118
|
end
|
102
119
|
end
|
@@ -16,7 +16,9 @@
|
|
16
16
|
:keep_reports: false
|
17
17
|
|
18
18
|
# Development settings (do not use)
|
19
|
+
#
|
19
20
|
# Override hostnames of incoming reports
|
20
21
|
#:override_hostname: report.example.com
|
22
|
+
#
|
21
23
|
# Store input payloads in a directory
|
22
24
|
#:incoming_save_dir: /var/lib/foreman-proxy/reports/incoming
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_reports
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lukas Zapletal
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Transform and upload Foreman host reports via REST API
|
14
14
|
email: lukas-x@zapletalovi.com
|