lrc_handler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ module ChefHandlerLrc
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'chef'
2
+ require "#{File.dirname(__FILE__)}/lbn_report_chef/version"
3
+ require "#{File.dirname(__FILE__)}/lbn_report_chef/lrc_hooks"
4
+
5
+ Chef::Config.send :extend, ChefHandlerLrc::LrcHooks
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: []