lrc_handler 0.1.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 +7 -0
- data/LICENSE.md +23 -0
- data/README.md +27 -0
- data/Rakefile +1 -0
- data/lib/lbn_report_chef/lrc_hooks.rb +56 -0
- data/lib/lbn_report_chef/lrc_reporting.rb +75 -0
- data/lib/lbn_report_chef/lrc_resource_reporter.rb +210 -0
- data/lib/lbn_report_chef/lrc_uploader.rb +23 -0
- data/lib/lbn_report_chef/version.rb +3 -0
- data/lib/lrc_handler.rb +5 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9473feeff833416f0bbce087dc54ef66a565d901
|
4
|
+
data.tar.gz: bbf0f51fb0c7ac76a79875462accac2d4cb08f4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60650906a31af87a9a9f906c8941edcfc8e7f4a74f6e2e081e654b377894af7a98f7e806e755d57fd71d6412fa53189098392026de3f4357fa054a59f4343e84
|
7
|
+
data.tar.gz: e575f7eab704a7e8bb4bb2bfab504531c5a774b7546a22bea8eec4eb2a828705a52fd48922a384d4d1646b56e025274248f12b826c377c4caa2d8126a46c53d9
|
data/LICENSE.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (c) LINKBYNET 2017
|
2
|
+
based on chef_handler_foreman gem package (Copyright (c) 2013 Marek Hulan)
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Description
|
2
|
+
|
3
|
+
This gem adds Chef report and attributes handlers that send reports to LRC Project.
|
4
|
+
LRC mean LinkByNet Reporter for Chef
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Since it's released as a gem you can simply run this command under root
|
9
|
+
|
10
|
+
```sh
|
11
|
+
chef gem install lrc_handler
|
12
|
+
```
|
13
|
+
|
14
|
+
## Usage:
|
15
|
+
|
16
|
+
In /etc/chef/client.rb:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# this adds new functions to chef configuration
|
20
|
+
require 'lrc_handler'
|
21
|
+
# here you can specify your connection options
|
22
|
+
lrc_server_options :url => 'http://your.server/report'
|
23
|
+
# add following line if you want to upload reports
|
24
|
+
lrc_reports_upload true
|
25
|
+
# add following line to manage reports verbosity. Allowed values are debug, notice and error
|
26
|
+
lrc_reports_level "notice"
|
27
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/lrc_reporting"
|
2
|
+
require "#{File.dirname(__FILE__)}/lrc_resource_reporter"
|
3
|
+
require "#{File.dirname(__FILE__)}/lrc_uploader"
|
4
|
+
|
5
|
+
module ChefHandlerLrc
|
6
|
+
module LrcHooks
|
7
|
+
attr_reader :lrc_uploader, :lrc_report_handler, :lrc_reporter
|
8
|
+
|
9
|
+
# Provide a chef-client cookbook friendly option
|
10
|
+
def lrc_server_url(url)
|
11
|
+
lrc_server_options(url: url)
|
12
|
+
end
|
13
|
+
|
14
|
+
# {:url => '', ...}
|
15
|
+
def lrc_server_options(options = {})
|
16
|
+
options[:client_key] = client_key || '/etc/chef/client.pem' unless options[:client_key]
|
17
|
+
raise 'No LRC URL! Please provide a URL' unless options[:url]
|
18
|
+
@lrc_uploader = LrcUploader.new(options)
|
19
|
+
# set uploader if handlers are already created
|
20
|
+
@lrc_report_handler.uploader = @lrc_uploader if @lrc_report_handler
|
21
|
+
@lrc_reporter.uploader = @lrc_uploader if @lrc_reporter
|
22
|
+
end
|
23
|
+
|
24
|
+
def lrc_reports_upload(upload, mode = 1)
|
25
|
+
if upload
|
26
|
+
case mode
|
27
|
+
when 1
|
28
|
+
@lrc_reporter = LrcResourceReporter.new(nil)
|
29
|
+
@lrc_reporter.uploader = @lrc_uploader
|
30
|
+
@lrc_reporter.log_level = @lrc_reports_level
|
31
|
+
if Chef::Config[:event_handlers].is_a?(Array)
|
32
|
+
Chef::Config[:event_handlers].push @lrc_reporter
|
33
|
+
else
|
34
|
+
Chef::Config[:event_handlers] = [@lrc_reporter]
|
35
|
+
end
|
36
|
+
when 2
|
37
|
+
@lrc_report_handler = LrcReporting.new
|
38
|
+
@lrc_report_handler.uploader = @lrc_uploader
|
39
|
+
report_handlers << @lrc_report_handler
|
40
|
+
exception_handlers << @lrc_report_handler
|
41
|
+
else
|
42
|
+
raise ArgumentError, 'unknown mode: ' + mode.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# level can be string error notice debug
|
48
|
+
def reports_log_level(level)
|
49
|
+
raise ArgumentError, 'unknown level: ' + level.to_s unless %w(error notice debug).include?(level)
|
50
|
+
@lrc_reports_level = level
|
51
|
+
if @lrc_reporter
|
52
|
+
@lrc_reporter.log_level = level
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'chef/handler'
|
2
|
+
|
3
|
+
module ChefHandlerLrc
|
4
|
+
class ForemanReporting < ::Chef::Handler
|
5
|
+
attr_accessor :uploader
|
6
|
+
|
7
|
+
def report
|
8
|
+
report = { 'host' => node.name, 'reported_at' => Time.now.utc.to_s }
|
9
|
+
report_status = Hash.new(0)
|
10
|
+
report_status['failed'] = 1 if failed?
|
11
|
+
report_status['applied'] = run_status.updated_resources.count
|
12
|
+
report['status'] = report_status
|
13
|
+
|
14
|
+
# I can't compute much metrics for now
|
15
|
+
metrics = {}
|
16
|
+
metrics['resources'] = { 'total' => run_status.all_resources.count }
|
17
|
+
|
18
|
+
times = {}
|
19
|
+
run_status.all_resources.each do |resource|
|
20
|
+
resource_name = resource.resource_name
|
21
|
+
if times[resource_name].nil?
|
22
|
+
times[resource_name] = resource.elapsed_time
|
23
|
+
else
|
24
|
+
times[resource_name] += resource.elapsed_time
|
25
|
+
end
|
26
|
+
end
|
27
|
+
metrics['time'] = times.merge!({ 'total' => run_status.elapsed_time })
|
28
|
+
report['metrics'] = metrics
|
29
|
+
|
30
|
+
logs = []
|
31
|
+
run_status.updated_resources.each do |resource|
|
32
|
+
l = { 'log' => { 'sources' => {}, 'messages' => {}, 'level' => 'notice' } }
|
33
|
+
|
34
|
+
case resource.resource_name.to_s
|
35
|
+
when 'template', 'cookbook_file'
|
36
|
+
message = resource.diff
|
37
|
+
when 'package'
|
38
|
+
message = "Installed #{resource.package_name} package in #{resource.version}"
|
39
|
+
else
|
40
|
+
message = resource.action.to_s
|
41
|
+
end
|
42
|
+
l['log']['messages']['message'] = message
|
43
|
+
l['log']['sources']['source'] = [resource.resource_name.to_s, resource.name].join(' ')
|
44
|
+
# Chef::Log.info("Diff is #{l['log']['messages']['message']}")
|
45
|
+
logs << l
|
46
|
+
end
|
47
|
+
|
48
|
+
# I only set failed to 1 if chef run failed
|
49
|
+
if failed?
|
50
|
+
logs << {
|
51
|
+
'log' => {
|
52
|
+
'sources' => { 'source' => 'chef' },
|
53
|
+
'messages' => { 'message' => run_status.exception },
|
54
|
+
'level' => 'err',
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
report['logs'] = logs
|
60
|
+
full_report = { 'report' => report }
|
61
|
+
|
62
|
+
send_report(full_report)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def send_report(report)
|
68
|
+
if uploader
|
69
|
+
uploader.foreman_request('/report', report, node.name)
|
70
|
+
else
|
71
|
+
Chef::Log.error 'No uploader registered for foreman reporting, skipping report upload'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module ChefHandlerLrc
|
2
|
+
class LrcResourceReporter < ::Chef::ResourceReporter
|
3
|
+
attr_accessor :uploader, :log_level
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@total_up_to_date = 0
|
7
|
+
@total_skipped = 0
|
8
|
+
@total_updated = 0
|
9
|
+
@total_failed = 0
|
10
|
+
@total_restarted = 0
|
11
|
+
@total_failed_restart = 0
|
12
|
+
@all_resources = []
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_started(run_status)
|
17
|
+
@run_status = run_status
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_completed(_node)
|
21
|
+
@status = 'success'
|
22
|
+
post_reporting_data
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_failed(exception)
|
26
|
+
@exception = exception
|
27
|
+
@status = 'failure'
|
28
|
+
# If we failed before we received the run_started callback, there's not much we can do
|
29
|
+
# in terms of reporting
|
30
|
+
if @run_status
|
31
|
+
post_reporting_data
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource_current_state_loaded(new_resource, action, current_resource)
|
36
|
+
super
|
37
|
+
@all_resources.push @pending_update unless @pending_update.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource_up_to_date(new_resource, action)
|
41
|
+
@total_up_to_date += 1
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def resource_skipped(resource, action, conditional)
|
46
|
+
@total_skipped += 1
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def resource_updated(new_resource, action)
|
51
|
+
@total_updated += 1
|
52
|
+
@total_restarted += 1 if action.to_s == 'restart'
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def resource_failed(new_resource, action, exception)
|
57
|
+
@total_failed += 1
|
58
|
+
@total_failed_restart += 1 if action.to_s == 'restart'
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
def resource_completed(new_resource)
|
63
|
+
if @pending_update && !nested_resource?(new_resource)
|
64
|
+
@pending_update.finish
|
65
|
+
@updated_resources << @pending_update
|
66
|
+
@pending_update = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def resource_bypassed(*args)
|
71
|
+
@why_run = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def post_reporting_data
|
75
|
+
if reporting_enabled?
|
76
|
+
run_data = prepare_run_data
|
77
|
+
Chef::Log.info('Sending resource update report to report API ...')
|
78
|
+
Chef::Log.debug run_data.inspect
|
79
|
+
begin
|
80
|
+
Chef::Log.debug('Sending data to report API ...')
|
81
|
+
if uploader
|
82
|
+
uploader.lrc_request('/report', { 'report' => run_data })
|
83
|
+
else
|
84
|
+
Chef::Log.error 'No uploader registered for LRC reporting, skipping report upload'
|
85
|
+
end
|
86
|
+
rescue => e
|
87
|
+
Chef::Log.error "Sending failed with #{e.class} #{e.message}"
|
88
|
+
Chef::Log.error e.backtrace.join("\n")
|
89
|
+
end
|
90
|
+
else
|
91
|
+
Chef::Log.debug('Reporting disabled, skipping report upload')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def prepare_run_data
|
96
|
+
run_data = {}
|
97
|
+
run_data['host'] = node_name.downcase
|
98
|
+
run_data['reported_at'] = end_time.to_s
|
99
|
+
run_data['status'] = resources_per_status
|
100
|
+
|
101
|
+
run_data['metrics'] = {
|
102
|
+
'resources' => { 'total' => @total_res_count },
|
103
|
+
'time' => resources_per_time,
|
104
|
+
}
|
105
|
+
|
106
|
+
run_data['logs'] = filter_logs(resources_logs + [chef_log])
|
107
|
+
run_data
|
108
|
+
end
|
109
|
+
|
110
|
+
def resources_per_status
|
111
|
+
if Chef::Config.why_run
|
112
|
+
{
|
113
|
+
'applied' => 0,
|
114
|
+
'restarted' => 0,
|
115
|
+
'failed' => 0,
|
116
|
+
'failed_restarts' => 0,
|
117
|
+
'skipped' => @total_skipped,
|
118
|
+
'pending' => @total_updated + @total_restarted + @total_failed + @total_failed_restart,
|
119
|
+
}
|
120
|
+
else
|
121
|
+
{
|
122
|
+
'applied' => @total_updated,
|
123
|
+
'restarted' => @total_restarted,
|
124
|
+
'failed' => @total_failed,
|
125
|
+
'failed_restarts' => @total_failed_restart,
|
126
|
+
'skipped' => @total_skipped,
|
127
|
+
'pending' => 0,
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def resources_per_time
|
133
|
+
@run_status.all_resources.inject({}) do |memo, resource|
|
134
|
+
name, time = resource.resource_name.to_s, resource.elapsed_time || 0
|
135
|
+
memo[name] = memo[name] ? memo[name] + time : time
|
136
|
+
memo
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def resources_logs
|
141
|
+
@all_resources.map do |resource|
|
142
|
+
action = resource.new_resource.action
|
143
|
+
message = action.is_a?(Array) ? action.first.to_s : action.to_s
|
144
|
+
message = format_message(message, resource.new_resource)
|
145
|
+
message += " (#{resource.exception.class} #{resource.exception.message})" unless resource.exception.nil?
|
146
|
+
level = resource_level(resource)
|
147
|
+
{ 'log' => {
|
148
|
+
'sources' => { 'source' => resource.new_resource.to_s },
|
149
|
+
'messages' => { 'message' => message },
|
150
|
+
'level' => level,
|
151
|
+
} }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def format_message(message, resource)
|
156
|
+
case resource.resource_name.to_s
|
157
|
+
when 'template', 'cookbook_file'
|
158
|
+
unless resource.diff.nil?
|
159
|
+
message += ' with diff ' + resource.diff.gsub('\\n', "\n")
|
160
|
+
end
|
161
|
+
when 'package'
|
162
|
+
message += " package in #{resource.version}" unless resource.version.nil?
|
163
|
+
else
|
164
|
+
message = resource.action.to_s
|
165
|
+
end
|
166
|
+
message
|
167
|
+
end
|
168
|
+
|
169
|
+
def resource_level(resource)
|
170
|
+
if !resource.exception.nil?
|
171
|
+
return 'err'
|
172
|
+
elsif resource.new_resource.updated
|
173
|
+
return 'notice'
|
174
|
+
else
|
175
|
+
return 'debug'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def chef_log
|
180
|
+
message = 'run'
|
181
|
+
if @status == 'success' && exception.nil?
|
182
|
+
level = 'notice'
|
183
|
+
else
|
184
|
+
message += " (#{exception.class} #{exception.message})"
|
185
|
+
level = 'err'
|
186
|
+
end
|
187
|
+
|
188
|
+
{ 'log' => {
|
189
|
+
'sources' => { 'source' => 'Chef' },
|
190
|
+
'messages' => { 'message' => message },
|
191
|
+
'level' => level,
|
192
|
+
} }
|
193
|
+
end
|
194
|
+
|
195
|
+
# currently we support only three log levels:
|
196
|
+
# 'debug' means do not filter,
|
197
|
+
# 'notice' updated resources and errors
|
198
|
+
# 'error' means only errors
|
199
|
+
|
200
|
+
def filter_logs(logs)
|
201
|
+
if log_level == 'error'
|
202
|
+
logs.select { |log| log['log']['level'] == 'err' }
|
203
|
+
elsif log_level == 'notice'
|
204
|
+
logs.select { |log| %w(err,notice).include? log['log']['level'] }
|
205
|
+
else
|
206
|
+
logs
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ChefHandlerLrc
|
5
|
+
class LrcUploader
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@options = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def lrc_request(path, body, method = 'post')
|
13
|
+
uri = URI.parse(options[:url])
|
14
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
15
|
+
req = Net::HTTP.const_get(method.capitalize).new("#{uri}#{path}")
|
16
|
+
req.add_field('Accept', 'application/json')
|
17
|
+
req.add_field('Content-Type', 'application/json')
|
18
|
+
req.body = body.to_json
|
19
|
+
response = http.request(req)
|
20
|
+
Chef::Log.info("The report API has return: #{response}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/lrc_handler.rb
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lrc_handler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- team-chef
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Chef handlers for lrc reporting
|
42
|
+
email:
|
43
|
+
- team-chef@linkbynet.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- LICENSE.md
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/lbn_report_chef/lrc_hooks.rb
|
52
|
+
- lib/lbn_report_chef/lrc_reporting.rb
|
53
|
+
- lib/lbn_report_chef/lrc_resource_reporter.rb
|
54
|
+
- lib/lbn_report_chef/lrc_uploader.rb
|
55
|
+
- lib/lbn_report_chef/version.rb
|
56
|
+
- lib/lrc_handler.rb
|
57
|
+
homepage: https://git.lbn.fr/chef-tools/lrc_handler
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.6.11
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: This gem adds chef handlers so your chef-client can send reports to API
|
81
|
+
test_files: []
|