chef_sous_vide 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +5 -3
- data/README.md +29 -12
- data/kitchen.yml +5 -0
- data/lib/sous_vide.rb +40 -5
- data/lib/sous_vide/event_methods.rb +20 -9
- data/lib/sous_vide/handler.rb +180 -60
- data/lib/sous_vide/outputs.rb +13 -0
- data/lib/sous_vide/outputs/json_file.rb +12 -11
- data/lib/sous_vide/outputs/json_http.rb +21 -8
- data/lib/sous_vide/outputs/logger.rb +6 -2
- data/lib/sous_vide/outputs/multi.rb +7 -3
- data/lib/sous_vide/tracked_resource.rb +64 -24
- data/lib/sous_vide/version.rb +1 -1
- data/sous_vide.gemspec +2 -2
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7625a1692ecb152693bc6dc151c671801001d59
|
4
|
+
data.tar.gz: 38a2599b444790b518ccec7c70d668b4bcc7b859
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a4a9001104866457fb7591800de2707122db72db4e5fa1f13c4444c8c6a13c82665823125afe5719b2b8486869d17e8111baace07afb6ea21bd9e7d578ac413
|
7
|
+
data.tar.gz: 19f0b7ad71404d464a687d4cbfabfc58f047734675b6f03038c8497d10c03d77a249594ece19a8806bd4f9f4be12e3d59f56ad799bb359ea154fde63acea3ce8
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
|
5
|
-
chef
|
4
|
+
chef_sous_vide (0.2.0)
|
5
|
+
chef
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
@@ -278,6 +278,7 @@ GEM
|
|
278
278
|
rubyzip (~> 1.1)
|
279
279
|
winrm (~> 2.0)
|
280
280
|
wmi-lite (1.0.2)
|
281
|
+
yard (0.9.19)
|
281
282
|
|
282
283
|
PLATFORMS
|
283
284
|
ruby
|
@@ -285,13 +286,14 @@ PLATFORMS
|
|
285
286
|
DEPENDENCIES
|
286
287
|
berkshelf
|
287
288
|
bundler (~> 1.17)
|
289
|
+
chef_sous_vide!
|
288
290
|
cucumber (~> 3.1.2)
|
289
291
|
kitchen-docker
|
290
292
|
kitchen-transport-rsync
|
291
293
|
pry
|
292
294
|
rake (~> 10.0)
|
293
|
-
sous_vide!
|
294
295
|
test-kitchen
|
296
|
+
yard
|
295
297
|
|
296
298
|
BUNDLED WITH
|
297
299
|
1.17.1
|
data/README.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
**=======> SousVide is not ready to use. <=======**
|
4
4
|
|
5
|
+
|
6
|
+
![SousVide example dashboard](media/kibana-dashboard.png?raw=true)
|
7
|
+
|
5
8
|
SousVide is a Chef Handler you can use to collect & visualize `chef-client` converge process. It receives event data from `chef-client` and keeps track of the converge process. It's essentially a stream parser hooked into `Chef::EventDispatch`.
|
6
9
|
|
7
10
|
At the end you will have an extensive report about events occured during the run.
|
@@ -24,7 +27,7 @@ Here's what SousVide will tell you:
|
|
24
27
|
- simple counters for each type
|
25
28
|
- notification type & notifying resource when available
|
26
29
|
|
27
|
-
All this and more will be available in a
|
30
|
+
All this and more will be available in a flat JSON data structure.
|
28
31
|
|
29
32
|
Feed it to Kibana, save to file or print at the end of chef-client run. `SousVide` comes with common outputs (see `SousVide::Outputs`) but you can write your own or even pass it a Proc.
|
30
33
|
|
@@ -33,12 +36,17 @@ Feed it to Kibana, save to file or print at the end of chef-client run. `SousVid
|
|
33
36
|
Add to your recipe:
|
34
37
|
|
35
38
|
```ruby
|
36
|
-
chef_gem "
|
39
|
+
chef_gem "chef_sous_vide" do
|
37
40
|
action :install
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
ruby_block "register sous handler" do
|
44
|
+
block do
|
45
|
+
require "sous_vide"
|
46
|
+
SousVide.register(node.run_context)
|
47
|
+
end
|
48
|
+
action :nothing
|
49
|
+
end.run_action(:run)
|
42
50
|
```
|
43
51
|
|
44
52
|
You should add these lines as early as possible. SousVide will not detect compile-time executions before it's registration, but otherwise it will work just fine (or just as one would expect).
|
@@ -81,19 +89,26 @@ Once SousVide is registered, it's for the most part up to you to consume the out
|
|
81
89
|
"chef_run_started_at": "2019-04-01 11:46:18",
|
82
90
|
"chef_run_completed_at": "2019-04-01 11:46:24",
|
83
91
|
"chef_run_success": true
|
84
|
-
}
|
85
|
-
|
86
|
-
#...
|
92
|
+
}
|
87
93
|
]
|
88
94
|
```
|
89
95
|
|
90
|
-
|
96
|
+
Example configuration for JsonHTTP:
|
91
97
|
|
92
98
|
```ruby
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
chef_gem "chef_sous_vide" do
|
100
|
+
action :install
|
101
|
+
end
|
102
|
+
|
103
|
+
ruby_block "register sous handler" do
|
104
|
+
block do
|
105
|
+
require "sous_vide"
|
106
|
+
json_http = SousVide::Outputs::JsonHTTP.new(url: "http://elasticsearch:3000")
|
107
|
+
SousVide.sous_output = json_http
|
108
|
+
SousVide.register(node.run_context)
|
109
|
+
end
|
110
|
+
action :nothing
|
111
|
+
end.run_action(:run)
|
97
112
|
```
|
98
113
|
|
99
114
|
Open `cookbooks/sous_vide/recipes/install.rb` to see how to configure other outputs or use more than one.
|
@@ -116,6 +131,8 @@ Once `chef-client` finishes converging you can access a Kibana dashboard and see
|
|
116
131
|
|
117
132
|
There are more example kitchen configurations you can converge and see the runs in Kibana. You can change the default recipe, converge again and see it in Kibana.
|
118
133
|
|
134
|
+
[![asciicast](https://asciinema.org/a/RerbmOQ5FzZisOM312zarxcYX.svg)](https://asciinema.org/a/RerbmOQ5FzZisOM312zarxcYX)
|
135
|
+
|
119
136
|
## Contributing
|
120
137
|
|
121
138
|
Bug reports, suggestions and pull requests are welcome on GitHub.
|
data/kitchen.yml
CHANGED
@@ -41,6 +41,8 @@ suites:
|
|
41
41
|
forward:
|
42
42
|
- "5601:5601" # kibana
|
43
43
|
- "9200:9200" # es
|
44
|
+
cap_add:
|
45
|
+
- IPC_LOCK
|
44
46
|
run_list:
|
45
47
|
- sous_vide::install
|
46
48
|
- sous_vide::default
|
@@ -70,6 +72,9 @@ suites:
|
|
70
72
|
java:
|
71
73
|
jdk_version: 8
|
72
74
|
- name: nginx
|
75
|
+
driver:
|
76
|
+
links:
|
77
|
+
- elasticsearch
|
73
78
|
includes:
|
74
79
|
- ubuntu-16.04
|
75
80
|
run_list:
|
data/lib/sous_vide.rb
CHANGED
@@ -1,10 +1,45 @@
|
|
1
|
-
require "chef/handler"
|
2
|
-
require "chef/http"
|
3
1
|
require "sous_vide/version"
|
4
|
-
|
5
|
-
require "sous_vide/tracked_resource"
|
6
2
|
require "sous_vide/handler"
|
3
|
+
require "chef"
|
7
4
|
|
5
|
+
# Interface to use SousVide in Chef.
|
6
|
+
#
|
7
|
+
# Provides a shortcut methods to configure and enable SousVide.
|
8
|
+
#
|
9
|
+
# @example Enable SousVide with JSON HTTP output and custom run name
|
10
|
+
#
|
11
|
+
# ruby_block "enable SousVide" do
|
12
|
+
# block do
|
13
|
+
# json_http_output = SousVide::Outputs::JsonHTTP.new(url: "http://localhost:3000")
|
14
|
+
# SousVide.run_name = "custom run name"
|
15
|
+
# SousVide.sous_output = json_http_output
|
16
|
+
# SousVide.register(node.run_context)
|
17
|
+
# end
|
18
|
+
# action :nothing
|
19
|
+
# end.run_action(:run)
|
8
20
|
module SousVide
|
9
|
-
|
21
|
+
# (see SousVide::Handler.register)
|
22
|
+
def self.register(chef_run_context)
|
23
|
+
SousVide::Handler.register(chef_run_context)
|
24
|
+
end
|
25
|
+
|
26
|
+
# (see SousVide::Handler#sous_output)
|
27
|
+
def self.sous_output=(output)
|
28
|
+
SousVide::Handler.instance.sous_output = output
|
29
|
+
end
|
30
|
+
|
31
|
+
# (see SousVide::Handler#run_name)
|
32
|
+
def self.run_name=(text)
|
33
|
+
SousVide::Handler.instance.run_name = text
|
34
|
+
end
|
35
|
+
|
36
|
+
# (see SousVide::Handler#run_id)
|
37
|
+
def self.run_id=(text)
|
38
|
+
SousVide::Handler.instance.run_id = text
|
39
|
+
end
|
40
|
+
|
41
|
+
# (see SousVide::Handler#logger)
|
42
|
+
def self.logger=(logger)
|
43
|
+
SousVide::Handler.instance.logger = logger
|
44
|
+
end
|
10
45
|
end
|
@@ -7,13 +7,15 @@ module SousVide
|
|
7
7
|
# 2. resource_* events (possibly multiple)
|
8
8
|
# 3. resource_action_complete
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
10
|
+
# If :resource_action_start assigned @processing_now only then other methods will perform any
|
11
|
+
# processing.
|
12
|
+
#
|
13
|
+
# :resource_action_completed unassigns @processing_now so all events will be ignored until
|
14
|
+
# :resource_action_start is called again.
|
15
|
+
#
|
16
|
+
# @see https://www.rubydoc.info/gems/chef/Chef/EventDispatch/Base
|
14
17
|
module EventMethods
|
15
|
-
#
|
16
|
-
# mode, notifications or skipped resources.
|
18
|
+
# Called before action is executed on a resource.
|
17
19
|
def resource_action_start(new_resource, action, notification_type, notifying_resource)
|
18
20
|
if nested?(new_resource) # ignore nested resources
|
19
21
|
new_r_name = "#{new_resource.resource_name}[#{new_resource.name}]##{action}"
|
@@ -53,6 +55,7 @@ module SousVide
|
|
53
55
|
true
|
54
56
|
end
|
55
57
|
|
58
|
+
# Called after a resource has been completely converged, but only if modifications were made.
|
56
59
|
def resource_updated(new_resource, _action)
|
57
60
|
return false if @processing_now.nil? || nested?(new_resource) # ignore nested resources
|
58
61
|
|
@@ -61,8 +64,7 @@ module SousVide
|
|
61
64
|
true
|
62
65
|
end
|
63
66
|
|
64
|
-
#
|
65
|
-
# `action :nothing` is used (it's a guard too).
|
67
|
+
# Called when a resource action has been skipped b/c of a conditional.
|
66
68
|
def resource_skipped(new_resource, _action, conditional)
|
67
69
|
return false if @processing_now.nil? || nested?(new_resource) # ignore nested resources
|
68
70
|
|
@@ -72,6 +74,7 @@ module SousVide
|
|
72
74
|
true
|
73
75
|
end
|
74
76
|
|
77
|
+
# Called when a resource has no converge actions, e.g., it was already correct.
|
75
78
|
def resource_up_to_date(new_resource, _action)
|
76
79
|
return false if @processing_now.nil? || nested?(new_resource) # ignore nested resources
|
77
80
|
|
@@ -80,6 +83,7 @@ module SousVide
|
|
80
83
|
true
|
81
84
|
end
|
82
85
|
|
86
|
+
# Called when a resource action has been completed.
|
83
87
|
def resource_completed(new_resource)
|
84
88
|
return false if @processing_now.nil? || nested?(new_resource) # ignore nested resources
|
85
89
|
|
@@ -110,7 +114,7 @@ module SousVide
|
|
110
114
|
# why-run mode and notifications are not in natural order and must not move the cursor.
|
111
115
|
#
|
112
116
|
# Having it pointing ahead is relevant because current resource has just been converged
|
113
|
-
# (
|
117
|
+
# (maybe 'failed') and it is not 'unprocessed'.
|
114
118
|
if !@processing_now.notifying_resource && # not notified
|
115
119
|
!::Chef::Config[:why_run] && # not why-run
|
116
120
|
@run_phase == "converge" # only converge phase
|
@@ -124,6 +128,7 @@ module SousVide
|
|
124
128
|
true
|
125
129
|
end
|
126
130
|
|
131
|
+
# Called when a resource fails and will not be retried.
|
127
132
|
def resource_failed(new_resource, _action, exception)
|
128
133
|
return false if @processing_now.nil? || nested?(new_resource) # ignore nested resources
|
129
134
|
|
@@ -134,6 +139,8 @@ module SousVide
|
|
134
139
|
true
|
135
140
|
end
|
136
141
|
|
142
|
+
# Called when a resource fails, but will retry.
|
143
|
+
#
|
137
144
|
# Resources with retries can succeed on subsequent attempts or ignore_failure option may be
|
138
145
|
# set and it's the only place we can capture intermittent errors.
|
139
146
|
#
|
@@ -148,6 +155,7 @@ module SousVide
|
|
148
155
|
true
|
149
156
|
end
|
150
157
|
|
158
|
+
# Called before convergence starts
|
151
159
|
def converge_start
|
152
160
|
debug("Received :converge_start")
|
153
161
|
debug("Changed run phase to 'converge'.")
|
@@ -155,6 +163,7 @@ module SousVide
|
|
155
163
|
@run_name ||= [@run_started_at, @chef_node_role, @chef_node_ipv4, @run_id].join(" ")
|
156
164
|
end
|
157
165
|
|
166
|
+
# Called when the converge phase is finished (success)
|
158
167
|
def converge_complete
|
159
168
|
debug("Received :converge_completed")
|
160
169
|
@run_success = true
|
@@ -162,6 +171,8 @@ module SousVide
|
|
162
171
|
send_to_output!
|
163
172
|
end
|
164
173
|
|
174
|
+
|
175
|
+
# Called if the converge phase fails
|
165
176
|
def converge_failed(_exception)
|
166
177
|
debug("Received :converge_failed")
|
167
178
|
@run_success = false
|
data/lib/sous_vide/handler.rb
CHANGED
@@ -1,55 +1,152 @@
|
|
1
|
+
require "sous_vide/tracked_resource"
|
1
2
|
require "sous_vide/event_methods"
|
2
|
-
require "sous_vide/outputs
|
3
|
-
require "sous_vide/outputs/json_http"
|
4
|
-
require "sous_vide/outputs/logger"
|
5
|
-
require "sous_vide/outputs/multi"
|
3
|
+
require "sous_vide/outputs"
|
6
4
|
|
7
5
|
require "securerandom"
|
8
6
|
require "singleton"
|
9
7
|
|
10
8
|
module SousVide
|
11
|
-
#
|
9
|
+
# SousVide exposes minimal API as it's driven by events passed from chef-client.
|
12
10
|
#
|
13
|
-
#
|
14
|
-
#
|
11
|
+
# It's sufficient to enable it and with default configuration it will print summary to
|
12
|
+
# chef-client logs.
|
15
13
|
#
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# Collected data can be sent to different outputs, ie. HTTP endpoint.
|
15
|
+
#
|
16
|
+
# :run_name and :run_id can be customized, it will be passed to the configured output along
|
17
|
+
# all data.
|
18
|
+
#
|
19
|
+
# @note You should interact with SousVide via top level methods, i.e. SousVide.run_id = '1123'.
|
20
|
+
#
|
21
|
+
# @example Enable SousVide with JSON HTTP output and custom run name
|
22
|
+
#
|
23
|
+
# ruby_block "enable SousVide" do
|
24
|
+
# block do
|
25
|
+
# json_http_output = SousVide::Outputs::JsonHTTP.new(url: "http://localhost:3000")
|
26
|
+
# SousVide::Handler.instance.run_name = "custom run name"
|
27
|
+
# SousVide::Handler.instance.sous_output = json_http_output
|
28
|
+
# SousVide::Handler.register(node.run_context)
|
29
|
+
# end
|
30
|
+
# action :nothing
|
31
|
+
# end.run_action(:run)
|
32
|
+
#
|
33
|
+
# @see SousVide::Handler.register
|
34
|
+
# @see SousVide::Handler#sous_output
|
35
|
+
# @see https://www.rubydoc.info/gems/chef/Chef/EventDispatch/Base
|
18
36
|
class Handler
|
19
37
|
include Singleton
|
20
38
|
include EventMethods
|
21
39
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
# Chef-client run phase. One of:
|
41
|
+
#
|
42
|
+
# * compile
|
43
|
+
# * converge
|
44
|
+
# * delayed
|
45
|
+
# * post-converge
|
46
|
+
#
|
47
|
+
# Compile & converge phases reflect standard Chef two-pass model. Delayed & post-converge
|
48
|
+
# are custom phases added by SousVide.
|
49
|
+
#
|
50
|
+
# SousVide enters delayed phase when chef-client begins processing delayed notifications, that
|
51
|
+
# is after all resources in the run list have bggen converged.
|
52
|
+
#
|
53
|
+
# Post-converge phase happens when chef-client run fails and aborts the converge process. It
|
54
|
+
# will include resources that should, but were not converged.
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
attr_reader :run_phase
|
30
60
|
|
31
|
-
#
|
61
|
+
# Chef::RunContext object from chef-client.
|
62
|
+
# @see https://www.rubydoc.info/gems/chef/Chef/RunContext
|
63
|
+
# @api private
|
64
|
+
attr_writer :chef_run_context
|
65
|
+
|
66
|
+
# Logger object SousVide will use (default Chef::Log)
|
32
67
|
#
|
33
|
-
#
|
68
|
+
# Event methods emit debug level messages and it can be helpful to configure a dedicated logger
|
69
|
+
# for SousVide to avoid excessive messages from chef-client.
|
70
|
+
#
|
71
|
+
# @example Configure custom debug logger
|
72
|
+
#
|
73
|
+
# logger = Logger.new(STDOUT)
|
74
|
+
# logger.level = Logger::DEBUG
|
75
|
+
# @sous_vide.logger = logger
|
76
|
+
#
|
77
|
+
# @see https://www.rubydoc.info/gems/chef/Chef/Log
|
78
|
+
attr_accessor :logger
|
79
|
+
|
80
|
+
# An object SousVide will pass all collected data to at the end of the chef-client run.
|
81
|
+
#
|
82
|
+
# The output must respond to #call (like Proc).
|
83
|
+
#
|
84
|
+
# Defaults to SousVide::Outputs::Logger.
|
85
|
+
#
|
86
|
+
# @example Configure JsonHTTP output
|
87
|
+
#
|
88
|
+
# json_http_output = SousVide::Outputs::JsonHTTP.new(url: "http://localhost:3000")
|
89
|
+
# @sous_vide.sous_output = json_http_output
|
90
|
+
#
|
91
|
+
# @example Configure multiple outputs
|
92
|
+
#
|
93
|
+
# json_file_output = SousVide::Outputs::JsonFile.new
|
94
|
+
# logger_output = SousVide::Outputs::Logger.new
|
95
|
+
# multi_output = SousVide::Outputs::Multi.new(json_file_output, logger_output)
|
96
|
+
# @sous_vide.sous_output = multi_output
|
97
|
+
#
|
98
|
+
#
|
99
|
+
# @see SousVide::Outputs::JsonFile
|
100
|
+
# @see SousVide::Outputs::JsonHTTP
|
101
|
+
# @see SousVide::Outputs::Logger
|
102
|
+
# @see SousVide::Outputs::Multi
|
103
|
+
attr_accessor :sous_output
|
104
|
+
|
105
|
+
|
106
|
+
# @return [SousVide::TrackedResource] a resource SousVide is currently processing.
|
107
|
+
# @api private
|
108
|
+
attr_reader :processing_now
|
109
|
+
|
110
|
+
# @return [Array<SousVide::TrackedResource>] a list of processed resources.
|
111
|
+
# @api private
|
112
|
+
attr_reader :processed
|
113
|
+
|
114
|
+
# SousVide custom run name. It will be included in the output, not used otherwise.
|
115
|
+
#
|
116
|
+
# @return [String]
|
117
|
+
attr_accessor :run_name
|
118
|
+
|
119
|
+
# SousVide custom run ID. It will be included in the output, not used otherwise.
|
120
|
+
#
|
121
|
+
# It is not chef client run id.
|
122
|
+
#
|
123
|
+
# @return [String]
|
124
|
+
attr_accessor :run_id
|
125
|
+
|
126
|
+
# Enables SousVide. It can be called anywhere in the recipe, ideally as early as possible.
|
34
127
|
#
|
35
128
|
# All converge-time resources will be included in the report regardless at what point
|
36
129
|
# registration happens.
|
37
130
|
#
|
38
131
|
# Compile-time resources defined before registration will not be included.
|
39
|
-
# TODO: see client.rb start_handlers as an option.
|
40
132
|
#
|
41
|
-
#
|
42
|
-
#
|
133
|
+
# @note chef_handler resource does not support subscribing to :events. Chef.event_handler DSL
|
134
|
+
# could be used but dealing with returns and exceptions in blocks is tricky. Using Chef API
|
135
|
+
# and Ruby feels the most reliable.
|
43
136
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
|
137
|
+
# @todo see client.rb start_handlers as an option.
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# SousVide::Handler.register(node.run_context)
|
141
|
+
#
|
142
|
+
# @return (void)
|
143
|
+
def self.register(chef_run_context)
|
47
144
|
::Chef::Log.info "Registering SousVide"
|
48
145
|
|
49
|
-
instance.chef_run_context =
|
50
|
-
instance.
|
146
|
+
instance.chef_run_context = chef_run_context
|
147
|
+
instance.populate_node_data
|
51
148
|
|
52
|
-
|
149
|
+
chef_run_context.events.register(instance)
|
53
150
|
end
|
54
151
|
|
55
152
|
def initialize
|
@@ -61,23 +158,17 @@ module SousVide
|
|
61
158
|
|
62
159
|
@run_started_at = Time.now.strftime("%F %T")
|
63
160
|
|
64
|
-
# Not related to RunStatus#run_id, it's
|
161
|
+
# Not related to RunStatus#run_id, it's SousVide internal run ID.
|
65
162
|
@run_id = SecureRandom.uuid.split("-").first # => 596e9d00
|
66
163
|
@run_phase = "compile"
|
67
164
|
|
68
|
-
# Default to Chef logger, but can be changed to anything that responds to #call
|
69
165
|
@logger = ::Chef::Log
|
70
166
|
@sous_output = Outputs::Logger.new(logger: @logger)
|
71
167
|
end
|
72
168
|
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
@chef_node_role = @chef_run_context.node["roles"].first || "<no role>"
|
77
|
-
@chef_node_instance_id = @chef_run_context.node.name
|
78
|
-
end
|
79
|
-
|
80
|
-
|
169
|
+
# Chef-client run related attributes.
|
170
|
+
#
|
171
|
+
# @return (Hash)
|
81
172
|
def run_data
|
82
173
|
{
|
83
174
|
chef_run_id: @run_id,
|
@@ -88,6 +179,9 @@ module SousVide
|
|
88
179
|
}
|
89
180
|
end
|
90
181
|
|
182
|
+
# Node related attributes
|
183
|
+
#
|
184
|
+
# @return (Hash)
|
91
185
|
def node_data
|
92
186
|
{
|
93
187
|
chef_node_ipv4: @chef_node_ipv4,
|
@@ -96,6 +190,31 @@ module SousVide
|
|
96
190
|
}
|
97
191
|
end
|
98
192
|
|
193
|
+
# Populates node attributes from Chef run context. These attributes will be passed to the
|
194
|
+
# configured output as :node_data.
|
195
|
+
#
|
196
|
+
# @api private
|
197
|
+
def populate_node_data
|
198
|
+
@chef_node_ipv4 = @chef_run_context.node["ipaddress"] || "<no ip>"
|
199
|
+
@chef_node_role = @chef_run_context.node["roles"].first || "<no role>"
|
200
|
+
@chef_node_instance_id = @chef_run_context.node.name
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
# Sends all collected data to configured output.
|
206
|
+
#
|
207
|
+
# It is called at the end of the converge process, both failure and success.
|
208
|
+
def send_to_output!
|
209
|
+
@sous_output.call(run_data: run_data, node_data: node_data, resources_data: @processed)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Creates SousVide::TrackedResource from Chef resource and action.
|
213
|
+
#
|
214
|
+
# @param chef_resource [Chef::Resource]
|
215
|
+
# @param action [Symbol] an action chef-client executes on the resource
|
216
|
+
#
|
217
|
+
# @return (SousVide::TrackedResource)
|
99
218
|
def create(chef_resource:, action:)
|
100
219
|
tracked = TrackedResource.new(action: action,
|
101
220
|
name: chef_resource.name,
|
@@ -108,30 +227,35 @@ module SousVide
|
|
108
227
|
tracked
|
109
228
|
end
|
110
229
|
|
111
|
-
|
112
|
-
#
|
230
|
+
|
231
|
+
# Tells if an event is related to the current SousVide resource. If not it's nested.
|
232
|
+
#
|
233
|
+
# Once a top level resource triggered :resource_action_start any events not related to it will
|
234
|
+
# be ignored by SousVide.
|
235
|
+
#
|
236
|
+
# @return (Boolean)
|
113
237
|
def nested?(chef_resource)
|
114
238
|
@processing_now &&
|
115
239
|
@processing_now.chef_resource_handle != chef_resource
|
116
240
|
end
|
117
241
|
|
118
|
-
#
|
242
|
+
# Backfills unprocessed resources. This is called only if chef-client failed (:converge_failed
|
243
|
+
# event). SousVide is now in "post-converge" run phase.
|
244
|
+
#
|
245
|
+
# Unprocessed resources as a result of notifications are not included.
|
119
246
|
def consume_unprocessed_resources!
|
120
247
|
all_known_resources = expand_chef_resources!
|
121
248
|
|
122
249
|
# No unprocessed resources left. Failure likely occured on last resource or in a delayed
|
123
250
|
# notification.
|
124
|
-
# TODO: check delayed notification failure
|
251
|
+
# TODO: check delayed notification failure. We may also want to backfeed unprocessed delayed
|
252
|
+
# notifications too? Maybe.
|
125
253
|
return if @resource_collection_cursor >= all_known_resources.size
|
126
254
|
|
127
255
|
unprocessed = all_known_resources[@resource_collection_cursor..-1]
|
128
256
|
|
129
|
-
#
|
130
|
-
# they will end up in @processed
|
131
|
-
# phase 'post-converge'.
|
132
|
-
#
|
133
|
-
# TODO: consider placing these resources before delayed notification, currently they are
|
134
|
-
# always at the very end. It _maybe_ makes sense.
|
257
|
+
# Pass unprocessed resources via :resource_action_start and :resource_completed so
|
258
|
+
# they will end up in @processed collection, but with status set to "unprocessed".
|
135
259
|
unprocessed.each do |tracked|
|
136
260
|
resource_action_start(
|
137
261
|
tracked.chef_resource_handle, # new_resource
|
@@ -146,34 +270,30 @@ module SousVide
|
|
146
270
|
end
|
147
271
|
end
|
148
272
|
|
149
|
-
# Resources with multiple actions must be expanded,
|
273
|
+
# Expands chef-client run list. Resources with multiple actions must be expanded,
|
274
|
+
# ie. given resource:
|
150
275
|
#
|
151
|
-
# service
|
276
|
+
# service "nginx" do
|
152
277
|
# action [:enable, :start]
|
153
278
|
# end
|
154
279
|
#
|
155
|
-
# After expansion we
|
280
|
+
# After expansion we will have 2 resources:
|
156
281
|
#
|
157
282
|
# * service[nginx] with action :enable
|
158
283
|
# * service[nginx] with action :start
|
159
284
|
#
|
160
|
-
#
|
161
|
-
#
|
285
|
+
# On failure SousVide will pick up unprocessed resources from here to feed the handler in
|
286
|
+
# post-converge stage.
|
162
287
|
#
|
163
|
-
# On a successful chef-client run
|
164
|
-
# be called.
|
288
|
+
# On a successful chef-client run this method won't be called.
|
165
289
|
def expand_chef_resources!
|
166
|
-
chef_run_context.resource_collection.flat_map do |chef_resource|
|
290
|
+
@chef_run_context.resource_collection.flat_map do |chef_resource|
|
167
291
|
Array(chef_resource.action).map do |action|
|
168
292
|
create(chef_resource: chef_resource, action: action)
|
169
293
|
end
|
170
294
|
end
|
171
295
|
end
|
172
296
|
|
173
|
-
def send_to_output!
|
174
|
-
@sous_output.call(run_data: run_data, node_data: node_data, resources_data: @processed)
|
175
|
-
end
|
176
|
-
|
177
297
|
def debug(*args)
|
178
298
|
message = args.compact.join(" ")
|
179
299
|
logger.debug(message)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "sous_vide/outputs/json_file"
|
2
|
+
require "sous_vide/outputs/json_http"
|
3
|
+
require "sous_vide/outputs/logger"
|
4
|
+
require "sous_vide/outputs/multi"
|
5
|
+
|
6
|
+
module SousVide
|
7
|
+
# @see SousVide::Outputs::JsonFile
|
8
|
+
# @see SousVide::Outputs::JsonHTTP
|
9
|
+
# @see SousVide::Outputs::Logger
|
10
|
+
# @see SousVide::Outputs::Multi
|
11
|
+
module Outputs
|
12
|
+
end
|
13
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
require "chef/config"
|
2
|
-
require "chef/json_compat"
|
3
|
-
require "chef/log"
|
4
|
-
|
5
1
|
module SousVide
|
6
2
|
module Outputs
|
7
|
-
# Saves the report to a JSON file
|
3
|
+
# Saves the report to a JSON file. The file will be saved to chef cache directory.
|
4
|
+
#
|
5
|
+
# Default file name is "sous-vide-report.json".
|
8
6
|
#
|
9
|
-
#
|
7
|
+
# @example
|
10
8
|
#
|
11
|
-
#
|
9
|
+
# SousVide::Outputs::JsonFile.new
|
12
10
|
class JsonFile
|
13
|
-
def initialize(logger:
|
11
|
+
def initialize(logger: nil, file_name: "sous-vide-report.json")
|
14
12
|
@logger = logger
|
13
|
+
@file_name = file_name
|
15
14
|
end
|
16
15
|
|
16
|
+
# Saves report to file.
|
17
17
|
def call(run_data:, node_data:, resources_data:)
|
18
18
|
log "=============== #{self.class.name} ==============="
|
19
19
|
log ""
|
@@ -23,13 +23,14 @@ module SousVide
|
|
23
23
|
tracked.to_h.merge(node_data).merge(run_data)
|
24
24
|
end
|
25
25
|
|
26
|
-
::Chef::FileCache.store(
|
27
|
-
::Chef::JSONCompat.to_json_pretty(json_data))
|
26
|
+
::Chef::FileCache.store(@file_name, ::Chef::JSONCompat.to_json_pretty(json_data))
|
28
27
|
|
29
|
-
log "The report is in #{Chef::Config[:file_cache_path]}
|
28
|
+
log "The report is in #{Chef::Config[:file_cache_path]}/#{@file_name} file."
|
30
29
|
log ""
|
31
30
|
end
|
32
31
|
|
32
|
+
private
|
33
|
+
|
33
34
|
def log(*args)
|
34
35
|
message = args.compact.join(" ")
|
35
36
|
logger.info(message)
|
@@ -6,16 +6,27 @@ module SousVide
|
|
6
6
|
module Outputs
|
7
7
|
# Makes a POST request to a configured endpoint. Logstash & Elasticsearch friendly format.
|
8
8
|
#
|
9
|
+
# It uses Net::HTTP to perform requests, it can be customized via :http_client accessor.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
9
13
|
# JsonHTTP.new(url: "http://localhost:9200/endpoint", max_retries: 10)
|
10
14
|
class JsonHTTP
|
11
|
-
|
15
|
+
# Provides access to Net::HTTP client object. Use it to enable SSL or pass your own client.
|
16
|
+
# @return [Net::HTTP]
|
17
|
+
attr_accessor :http_client
|
18
|
+
|
19
|
+
# @param max_retries [Fixnum] number retries across all requests made.
|
20
|
+
def initialize(url:, max_retries: 2, logger: nil)
|
12
21
|
@endpoint = URI(url)
|
13
22
|
@logger = logger
|
14
|
-
|
15
|
-
@max_retries = max_retries
|
23
|
+
@retry = 0
|
24
|
+
@max_retries = max_retries
|
16
25
|
@http_client = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
17
26
|
end
|
18
27
|
|
28
|
+
# Sends a POST request with a JSON payload using @http_client object per resource.
|
29
|
+
# @return (void)
|
19
30
|
def call(run_data:, node_data:, resources_data:)
|
20
31
|
log "=============== #{self.class.name} ==============="
|
21
32
|
log ""
|
@@ -38,24 +49,26 @@ module SousVide
|
|
38
49
|
log ""
|
39
50
|
end
|
40
51
|
|
52
|
+
private
|
53
|
+
|
41
54
|
def call_with_retries(nethttp_request)
|
42
|
-
_retry = 0
|
43
55
|
begin
|
44
56
|
@http_client.request(nethttp_request)
|
45
57
|
rescue
|
46
|
-
if
|
47
|
-
|
58
|
+
if @retry < @max_retries
|
59
|
+
logger.warn("Request failed, retry #{@retry} of #{@max_retries}.")
|
60
|
+
@retry += 1
|
48
61
|
sleep 2
|
49
62
|
retry
|
50
63
|
else
|
64
|
+
logger.error("Request failed, retry #{@retry} of #{@max_retries}. Abort.")
|
51
65
|
raise
|
52
66
|
end
|
53
67
|
end
|
54
68
|
end
|
55
69
|
|
56
70
|
def log(*args)
|
57
|
-
|
58
|
-
logger.info(message)
|
71
|
+
logger.info(args.compact.join(" "))
|
59
72
|
end
|
60
73
|
|
61
74
|
def logger
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module SousVide
|
2
2
|
module Outputs
|
3
|
-
# Prints the report to
|
3
|
+
# Prints the report to logger.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# @example
|
6
|
+
# SousVide::Outputs::Logger.new
|
6
7
|
class Logger
|
7
8
|
def initialize(logger: nil)
|
8
9
|
@logger = logger
|
9
10
|
end
|
10
11
|
|
12
|
+
# Prints the report to logger.
|
11
13
|
def call(run_data:, node_data:, resources_data:)
|
12
14
|
log "=============== #{self.class.name} ==============="
|
13
15
|
log ""
|
@@ -35,6 +37,8 @@ module SousVide
|
|
35
37
|
log ""
|
36
38
|
end
|
37
39
|
|
40
|
+
private
|
41
|
+
|
38
42
|
def log(*args)
|
39
43
|
message = args.compact.join(' ')
|
40
44
|
logger.info(message)
|
@@ -2,14 +2,18 @@ module SousVide
|
|
2
2
|
module Outputs
|
3
3
|
# Combines multiple outputs
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# @example
|
6
|
+
# http = JsonHTTP.new(url: "http://localhost:9200/endpoint")
|
7
|
+
# logger = SousVide::Outputs::Logger.new
|
8
|
+
# file = SousVide::Outputs::JsonFile.new
|
9
|
+
#
|
10
|
+
# SousVide::Outputs::Multi.new(logger, file, http)
|
8
11
|
class Multi
|
9
12
|
def initialize(*outputs)
|
10
13
|
@outputs = outputs
|
11
14
|
end
|
12
15
|
|
16
|
+
# Calls all configured outputs in order.
|
13
17
|
def call(run_data:, node_data:, resources_data:)
|
14
18
|
@outputs.each do |output|
|
15
19
|
output.call(run_data: run_data, node_data: node_data,
|
@@ -1,31 +1,67 @@
|
|
1
1
|
module SousVide
|
2
|
-
#
|
2
|
+
# It is a very simple data structure SousVide uses to capture information about resource
|
3
|
+
# execution.
|
3
4
|
#
|
4
|
-
#
|
5
|
-
# information.
|
5
|
+
# @see https://www.rubydoc.info/gems/chef/Chef/Resource
|
6
6
|
class TrackedResource
|
7
|
-
attr_accessor :type,
|
8
|
-
:name,
|
9
|
-
:action,
|
10
|
-
:status,
|
11
|
-
:duration_ms,
|
12
|
-
:guard_description,
|
13
|
-
:execution_phase,
|
14
|
-
:execution_order,
|
15
|
-
:notifying_resource,
|
16
|
-
:notification_type,
|
17
|
-
:before_notifications,
|
18
|
-
:immediate_notifications,
|
19
|
-
:delayed_notifications,
|
20
|
-
:retries,
|
21
|
-
:error_output,
|
22
|
-
:error_source,
|
23
|
-
:cookbook_name,
|
24
|
-
:cookbook_recipe,
|
25
|
-
:source_line,
|
26
|
-
:started_at,
|
27
|
-
:completed_at
|
28
7
|
|
8
|
+
attr_accessor :type
|
9
|
+
|
10
|
+
attr_accessor :name
|
11
|
+
|
12
|
+
# Action executed on this resource.
|
13
|
+
attr_accessor :action
|
14
|
+
|
15
|
+
# Result of the action, ie. "updated", "skipped", ...
|
16
|
+
attr_accessor :status
|
17
|
+
|
18
|
+
attr_accessor :duration_ms
|
19
|
+
|
20
|
+
attr_accessor :guard_description
|
21
|
+
|
22
|
+
# SousVide run-phase when this resource was executed.
|
23
|
+
attr_accessor :execution_phase
|
24
|
+
|
25
|
+
# Resource execution order. Takes into account notifications and does not reflect resource.
|
26
|
+
# order on the run list.
|
27
|
+
attr_accessor :execution_order
|
28
|
+
|
29
|
+
# Resource that triggered this execution. Delayed notifications do not have this.
|
30
|
+
attr_accessor :notifying_resource
|
31
|
+
|
32
|
+
attr_accessor :notification_type
|
33
|
+
|
34
|
+
# Number of before notifications attached to this resource.
|
35
|
+
attr_accessor :before_notifications
|
36
|
+
|
37
|
+
# Number of immediate notifications attached to this resource.
|
38
|
+
attr_accessor :immediate_notifications
|
39
|
+
|
40
|
+
# Number of delayed notifications attached to this resource.
|
41
|
+
attr_accessor :delayed_notifications
|
42
|
+
|
43
|
+
# Number of retries.
|
44
|
+
attr_accessor :retries
|
45
|
+
|
46
|
+
# Last error output if available. This can be populated when resource failed and when resource
|
47
|
+
# succeed after a retry.
|
48
|
+
attr_accessor :error_output
|
49
|
+
|
50
|
+
# Resource definition that triggered the error.
|
51
|
+
attr_accessor :error_source
|
52
|
+
|
53
|
+
attr_accessor :cookbook_name
|
54
|
+
|
55
|
+
attr_accessor :cookbook_recipe
|
56
|
+
|
57
|
+
attr_accessor :source_line
|
58
|
+
|
59
|
+
attr_accessor :started_at
|
60
|
+
|
61
|
+
attr_accessor :completed_at
|
62
|
+
|
63
|
+
# Chef API resource. It is used for comparsion only.
|
64
|
+
# @api private
|
29
65
|
attr_accessor :chef_resource_handle
|
30
66
|
|
31
67
|
def initialize(name:, action:, type:)
|
@@ -42,10 +78,14 @@ module SousVide
|
|
42
78
|
@error_source = nil
|
43
79
|
end
|
44
80
|
|
81
|
+
# String and human friendly represtnation of the resource
|
82
|
+
# @return [String]
|
45
83
|
def to_s
|
46
84
|
"#{@type}[#{@name}]##{@action}"
|
47
85
|
end
|
48
86
|
|
87
|
+
# Serializes the resource to hash
|
88
|
+
# @return [Hash]
|
49
89
|
def to_h
|
50
90
|
{
|
51
91
|
chef_resource: "#{@type}[#{@name}]##{@action}",
|
data/lib/sous_vide/version.rb
CHANGED
data/sous_vide.gemspec
CHANGED
@@ -15,14 +15,14 @@ Gem::Specification.new do |spec|
|
|
15
15
|
|
16
16
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
17
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
18
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(cookbooks|kitchen|features)/}) }
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(cookbooks|kitchen|features|media)/}) }
|
19
19
|
end
|
20
20
|
|
21
21
|
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_dependency "chef"
|
25
|
+
spec.add_dependency "chef"
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.17"
|
28
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef_sous_vide
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- robuye
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -159,6 +159,7 @@ files:
|
|
159
159
|
- lib/sous_vide.rb
|
160
160
|
- lib/sous_vide/event_methods.rb
|
161
161
|
- lib/sous_vide/handler.rb
|
162
|
+
- lib/sous_vide/outputs.rb
|
162
163
|
- lib/sous_vide/outputs/json_file.rb
|
163
164
|
- lib/sous_vide/outputs/json_http.rb
|
164
165
|
- lib/sous_vide/outputs/logger.rb
|