ey_enzyme 0.9.48 → 2.0.7
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/lib/ey_enzyme/api/client.rb +133 -0
- data/lib/ey_enzyme/api.rb +29 -102
- data/lib/ey_enzyme/cli.rb +125 -68
- data/lib/ey_enzyme/cookbook_set.rb +121 -43
- data/lib/ey_enzyme/multi_logger.rb +19 -0
- data/lib/ey_enzyme/version.rb +1 -1
- data/lib/ey_enzyme.rb +2 -2
- metadata +117 -116
- data/bin/ey-recipes +0 -5
- data/lib/ey_enzyme/old_cli.rb +0 -35
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c71e1af54ddfa6f64786576598b0f828f1528bff
|
4
|
+
data.tar.gz: 8586d600ca185ccc67b491393f4b0bba56bd445e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6e747c25787291bb9dffdf1077a645933885ad23bd5f97df67190ee3c22968dc08f11ad025fc7b84ff44fc8196ad0a0de5088e4c780ffb2db9f157ebbeb7f97
|
7
|
+
data.tar.gz: 7ff5be0a8cfc497cabf97c30ff01c0ea497210829dbb71fdaf48414d8ad917ce563610d0e43a2194188937a3e65d91fa877a3a0e14ce13f68ac817bfad801f40
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'rest_client'
|
3
|
+
|
4
|
+
module EY
|
5
|
+
module Enzyme
|
6
|
+
module API
|
7
|
+
class Client
|
8
|
+
|
9
|
+
REST_TIMEOUT_SECONDS = 60
|
10
|
+
TOTAL_TIMEOUT_SECONDS = 600
|
11
|
+
RETRY_TIMEOUT_SECONDS = 30
|
12
|
+
|
13
|
+
attr_reader :api_url, :instance_id, :token, :logger
|
14
|
+
|
15
|
+
def initialize(api_url, instance_id, token, logger)
|
16
|
+
@api_url = api_url
|
17
|
+
@instance_id = instance_id
|
18
|
+
@token = token
|
19
|
+
@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def rest
|
23
|
+
@rest ||= RestClient::Resource.new(
|
24
|
+
api_url,
|
25
|
+
:timeout => REST_TIMEOUT_SECONDS
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def report(message)
|
30
|
+
safe_api("report", :message => message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def notify_success
|
34
|
+
retry_api("completed", :status => 'true')
|
35
|
+
end
|
36
|
+
|
37
|
+
def notify_error(type, error)
|
38
|
+
log :exception, "Notifying #{type} error", error
|
39
|
+
call_api("error", params_for(type, error))
|
40
|
+
rescue RestClient::Exception
|
41
|
+
log :exception, "Failed to notify of #{type} error", $!
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
|
45
|
+
def dna
|
46
|
+
retry_api("dna")
|
47
|
+
end
|
48
|
+
|
49
|
+
def custom_recipe_url
|
50
|
+
if response = retry_api("custom-recipe")
|
51
|
+
response["url"]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#upload, as in, upload the log file output of the chef run
|
56
|
+
def upload(type, file)
|
57
|
+
retry_api("store_gzip", :type => type, :file => file)
|
58
|
+
rescue => e
|
59
|
+
#blanket rescue feels dangerous, but we're at least logging it
|
60
|
+
#we don't have all the information we'd like to include in the exception message here anyway
|
61
|
+
#caller (CookbookSet#upload) should check return value and notify failure
|
62
|
+
api_error("store_gzip", e)
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def log(level, *msg)
|
69
|
+
@logger.send(level, *msg) if @logger
|
70
|
+
end
|
71
|
+
|
72
|
+
def keys
|
73
|
+
@keys ||= {:instance_id => instance_id, :token => token}
|
74
|
+
end
|
75
|
+
|
76
|
+
def params_for(type, error)
|
77
|
+
{ :class => error.class,
|
78
|
+
:message => error.message,
|
79
|
+
:backtrace => error.backtrace,
|
80
|
+
:where_failed => type }
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_api(path, opts = {})
|
84
|
+
log :debug, "API call path: #{path.inspect}, data: #{opts.inspect}"
|
85
|
+
JSON.parse(
|
86
|
+
rest[path].post(
|
87
|
+
opts.merge(keys),
|
88
|
+
{
|
89
|
+
:content_type => "application/json",
|
90
|
+
:accept => "application/json"
|
91
|
+
}
|
92
|
+
)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def retry_api(path, opts={})
|
97
|
+
Timeout.timeout(TOTAL_TIMEOUT_SECONDS) do
|
98
|
+
loop do
|
99
|
+
if result = safe_api(path, opts)
|
100
|
+
return result
|
101
|
+
end
|
102
|
+
sleep RETRY_TIMEOUT_SECONDS
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue Timeout::Error
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def safe_api(path, opts = {})
|
110
|
+
begin
|
111
|
+
return call_api(path, opts)
|
112
|
+
rescue RestClient::BadGateway, RestClient::GatewayTimeout
|
113
|
+
api_error(path, $!)
|
114
|
+
return false
|
115
|
+
rescue RestClient::ServiceUnavailable => e
|
116
|
+
response = e.response
|
117
|
+
if response.headers[:content_type] =~ /json/
|
118
|
+
hash = JSON.parse(response)
|
119
|
+
log :error, "Failed to fetch DNA: #{hash.delete('message')}: #{hash.inspect}"
|
120
|
+
end
|
121
|
+
|
122
|
+
api_error(path, e)
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def api_error(path, error)
|
128
|
+
log :exception, "API request failed", error
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/ey_enzyme/api.rb
CHANGED
@@ -1,117 +1,44 @@
|
|
1
|
-
|
1
|
+
require 'ey_enzyme/api/client'
|
2
2
|
|
3
|
-
module EY
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@instance_id = instance_id
|
8
|
-
@token = token
|
9
|
-
@logger = MultiLogger.new(log_file)
|
10
|
-
@rest = RestClient::Resource.new(api_url, :timeout => api_rest_timeout_seconds)
|
11
|
-
end
|
12
|
-
|
13
|
-
def report(message)
|
14
|
-
safe_api("report", :message => message)
|
15
|
-
end
|
16
|
-
|
17
|
-
def notify_success
|
18
|
-
retry_api("completed", :status => 'true')
|
19
|
-
end
|
20
|
-
|
21
|
-
def notify_error(type, error)
|
22
|
-
@logger.exception "Notifying #{type} error", error
|
23
|
-
call_api("error", params_for(type, error))
|
24
|
-
rescue RestClient::Exception
|
25
|
-
@logger.exception "Failed to notify of #{type} error", $!
|
26
|
-
raise
|
27
|
-
end
|
28
|
-
|
29
|
-
def dna
|
30
|
-
retry_api("dna")
|
31
|
-
end
|
32
|
-
|
33
|
-
def custom_recipe_url
|
34
|
-
if response = retry_api("custom-recipe")
|
35
|
-
response["url"]
|
3
|
+
module EY
|
4
|
+
module Enzyme
|
5
|
+
module API
|
6
|
+
class Unconfigured < StandardError
|
36
7
|
end
|
37
|
-
end
|
38
|
-
|
39
|
-
#upload, as in, upload the log file output of the chef run
|
40
|
-
def upload(type, content)
|
41
|
-
content = content.force_encoding('UTF-8') if content.respond_to?(:force_encoding)
|
42
|
-
retry_api("store", :type => type, :content => content)
|
43
|
-
rescue => e
|
44
|
-
#blanket rescue feels dangerous, but we're at least logging it
|
45
|
-
#we don't have all the information we'd like to include in the exception message here anyway
|
46
|
-
#caller (CookbookSet#upload) should check return value and notify failure
|
47
|
-
api_error("store", e)
|
48
|
-
false
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def keys
|
54
|
-
@keys ||= {:instance_id => @instance_id, :token => @token}
|
55
|
-
end
|
56
|
-
|
57
|
-
def params_for(type, error)
|
58
|
-
{ :class => error.class,
|
59
|
-
:message => error.message,
|
60
|
-
:backtrace => error.backtrace,
|
61
|
-
:where_failed => type }
|
62
|
-
end
|
63
8
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
9
|
+
def self.configure(api_url, instance_id, token, logger)
|
10
|
+
@client = Client.new(api_url, instance_id, token, logger)
|
11
|
+
self
|
12
|
+
end
|
69
13
|
|
70
|
-
|
71
|
-
|
72
|
-
|
14
|
+
def self.client
|
15
|
+
raise Unconfigured unless @client
|
16
|
+
@client
|
17
|
+
end
|
73
18
|
|
74
|
-
|
75
|
-
|
76
|
-
|
19
|
+
def self.report(message)
|
20
|
+
client.report(message)
|
21
|
+
end
|
77
22
|
|
78
|
-
|
79
|
-
|
80
|
-
|
23
|
+
def self.notify_success
|
24
|
+
client.notify_success
|
25
|
+
end
|
81
26
|
|
82
|
-
|
83
|
-
|
84
|
-
loop do
|
85
|
-
if result = safe_api(path, opts)
|
86
|
-
return result
|
87
|
-
end
|
88
|
-
sleep api_sleep_before_retry_seconds
|
89
|
-
end
|
27
|
+
def self.notify_error(type, error)
|
28
|
+
client.notify_error(type, error)
|
90
29
|
end
|
91
|
-
rescue Timeout::Error
|
92
|
-
false
|
93
|
-
end
|
94
30
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
rescue RestClient::BadGateway, RestClient::GatewayTimeout
|
99
|
-
api_error(path, $!)
|
100
|
-
return false
|
101
|
-
rescue RestClient::ServiceUnavailable => e
|
102
|
-
response = e.response
|
103
|
-
if response.headers[:content_type] =~ /json/
|
104
|
-
hash = JSON.parse(response)
|
105
|
-
@logger.error "Failed to fetch DNA: #{hash.delete('message')}: #{hash.inspect}"
|
106
|
-
end
|
31
|
+
def self.dna
|
32
|
+
client.dna
|
33
|
+
end
|
107
34
|
|
108
|
-
|
109
|
-
|
35
|
+
def self.custom_recipe_url
|
36
|
+
client.custom_recipe_url
|
110
37
|
end
|
111
|
-
end
|
112
38
|
|
113
|
-
|
114
|
-
|
39
|
+
def self.upload(type, file)
|
40
|
+
client.upload(type, file)
|
41
|
+
end
|
115
42
|
end
|
116
43
|
end
|
117
44
|
end
|
data/lib/ey_enzyme/cli.rb
CHANGED
@@ -5,44 +5,66 @@ module EY::Enzyme
|
|
5
5
|
def self.run(args)
|
6
6
|
$stdout.sync = true
|
7
7
|
|
8
|
-
defaults = {
|
9
|
-
|
8
|
+
defaults = {
|
9
|
+
chef_bin: 'chef-client',
|
10
|
+
run_list: ['recipe[ey-init::main]'],
|
11
|
+
config: '/etc/engineyard/dracul.yml',
|
12
|
+
base_path: '/etc/chef/',
|
13
|
+
logfile: '/var/log/ey-enzyme.log',
|
14
|
+
log_level: :info,
|
15
|
+
keep: 5,
|
16
|
+
run_custom: true,
|
17
|
+
download: true,
|
18
|
+
mode: :deploy
|
19
|
+
}
|
10
20
|
options = {}
|
11
21
|
|
12
22
|
opts = OptionParser.new do |opts|
|
13
23
|
opts.version = EY::Enzyme::VERSION
|
14
|
-
opts.banner = "Usage: ey-enzyme
|
24
|
+
opts.banner = "Usage: ey-enzyme <options> [argument]"
|
15
25
|
opts.define_head "ey-enzyme: running recipes..."
|
16
26
|
opts.separator '*'*80
|
17
27
|
|
18
|
-
opts.on("--
|
19
|
-
options[:quick] = true
|
20
|
-
end
|
21
|
-
|
22
|
-
opts.on("--logfile", "Set the logfile (defaults to /var/log/enzyme.log)") do |path|
|
28
|
+
opts.on("--logfile", "Set the logfile (default: #{defaults[:logfile]})") do |path|
|
23
29
|
options[:logfile] = path
|
24
30
|
end
|
25
31
|
|
26
|
-
opts.on("--config CONFIG", "Use config file") do |config|
|
32
|
+
opts.on("--config CONFIG", "Use config file (default: #{defaults[:config]})") do |config|
|
27
33
|
options[:config] = config
|
28
34
|
end
|
29
35
|
|
30
|
-
opts.on("--
|
31
|
-
options[:
|
36
|
+
opts.on("--cookbooks PATH", "Use the given path for retrieving and using the cookbooks (default: #{defaults[:base_path]}") do |path|
|
37
|
+
options[:base_path] = path
|
32
38
|
end
|
33
39
|
|
34
|
-
opts.on("--
|
35
|
-
options[:
|
40
|
+
opts.on("--chef-bin PATH", "Use this chef-solo executable to run recipes") do |binary|
|
41
|
+
options[:chef_bin] = binary
|
36
42
|
end
|
37
43
|
|
38
|
-
opts.on("--
|
39
|
-
options[:
|
44
|
+
opts.on("--run-list RECIPES", "Comma-separated list of recipes to run") do |recipe_list|
|
45
|
+
options[:run_list] = recipe_list.split(',').collect {|r| "recipe[#{r}]" }
|
40
46
|
end
|
41
47
|
|
42
48
|
opts.on("--report message", "Report a status") do |message|
|
43
|
-
options[:
|
49
|
+
options[:mode] = :report
|
44
50
|
options[:message] = message
|
45
51
|
end
|
52
|
+
|
53
|
+
opts.on("--no-custom", "Do not overlay custom recipes") do
|
54
|
+
options[:run_custom] = false
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on("--cached", "Used the cached version of the cookbooks and dna.json") do
|
58
|
+
options[:download] = false
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on("--refresh-dna", "Write new dna even if --cached is on") do
|
62
|
+
options[:refresh_dna] = true
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on("--verbose", "Show debug logs as well") do
|
66
|
+
options[:log_level] = :debug
|
67
|
+
end
|
46
68
|
end
|
47
69
|
|
48
70
|
opts.parse!(args)
|
@@ -50,43 +72,57 @@ module EY::Enzyme
|
|
50
72
|
config = options[:config] || defaults[:config]
|
51
73
|
cli = new(defaults.merge(YAML.load_file(config)).merge(options))
|
52
74
|
|
53
|
-
if options[:report
|
75
|
+
if options[:mode] == :report
|
54
76
|
cli.report(options[:message])
|
55
|
-
elsif options[:type]
|
56
|
-
cli.deploy
|
57
77
|
else
|
58
|
-
|
78
|
+
cli.deploy
|
59
79
|
end
|
60
80
|
|
61
|
-
|
62
81
|
rescue OptionParser::InvalidOption
|
63
82
|
$stderr.puts "#{File.basename($0)} #{$!.message}"
|
64
83
|
abort opts.to_s
|
84
|
+
|
65
85
|
rescue *IGNORABLE_EXCEPTIONS
|
66
86
|
raise
|
87
|
+
|
67
88
|
rescue DeployError
|
68
89
|
if cli
|
90
|
+
cli.report $!.message
|
69
91
|
cli.notify_user_error($!)
|
70
92
|
exit 0
|
71
93
|
end
|
72
94
|
raise
|
95
|
+
|
96
|
+
rescue DNAError
|
97
|
+
if cli
|
98
|
+
cli.report $!.message
|
99
|
+
cli.notify_system_error($!)
|
100
|
+
exit 0
|
101
|
+
end
|
102
|
+
raise
|
103
|
+
|
73
104
|
rescue Exception
|
74
105
|
if cli
|
75
106
|
cli.notify_system_error($!)
|
76
107
|
exit 0
|
77
108
|
end
|
78
109
|
raise
|
110
|
+
|
79
111
|
end
|
80
112
|
|
81
113
|
def initialize(opts={})
|
82
114
|
@opts = opts
|
83
|
-
@
|
84
|
-
@
|
115
|
+
@logger = opts[:logger] = MultiLogger.new(opts[:logfile])
|
116
|
+
@api = API
|
117
|
+
@api.configure(opts[:api], opts[:instance_id], opts[:token], opts[:logger])
|
118
|
+
|
119
|
+
@logger.level = opts[:log_level]
|
85
120
|
|
86
121
|
@opts[:timestamp] = Time.now.strftime("%Y-%m-%dT%H-%M-%S")
|
87
122
|
end
|
88
123
|
|
89
124
|
def report(message)
|
125
|
+
@logger.debug "REPORT MESSAGE: #{message}"
|
90
126
|
@api.report(message)
|
91
127
|
end
|
92
128
|
|
@@ -98,74 +134,95 @@ module EY::Enzyme
|
|
98
134
|
@api.notify_error("user", error)
|
99
135
|
end
|
100
136
|
|
101
|
-
MAIN_RECIPE_PATH = '/etc/chef/recipes'
|
102
|
-
CUSTOM_RECIPE_PATH = '/etc/chef-custom/recipes'
|
103
|
-
|
104
137
|
def deploy
|
105
138
|
@logger.info "Starting configuration run"
|
106
139
|
|
107
|
-
|
108
|
-
|
109
|
-
case @opts[:type]
|
110
|
-
when :main
|
111
|
-
run_main
|
112
|
-
run_custom(json)
|
113
|
-
when :custom
|
114
|
-
run_custom(json)
|
115
|
-
else
|
116
|
-
raise "Unknown type: #{@opts[:type].inspect}"
|
117
|
-
end
|
140
|
+
ensure_resolv_conf_is_functional
|
141
|
+
update_dna
|
118
142
|
|
143
|
+
run_cookbooks
|
119
144
|
@api.notify_success
|
120
145
|
end
|
121
146
|
|
122
|
-
def
|
123
|
-
|
124
|
-
main_cookbooks.run
|
147
|
+
def cookbook_set
|
148
|
+
CookbookSet.new(@api, @opts)
|
125
149
|
end
|
126
150
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
151
|
+
private
|
152
|
+
|
153
|
+
def run_cookbooks
|
154
|
+
report "Initializing cookbook run"
|
155
|
+
cookbook_set.run
|
156
|
+
end
|
131
157
|
|
132
|
-
|
158
|
+
## Resovlve resolv.conf stuff
|
133
159
|
|
134
|
-
|
160
|
+
def ensure_resolv_conf_is_functional
|
161
|
+
@logger.info "Checking resolv.conf"
|
162
|
+
unless does_hostname_resolve?
|
163
|
+
modify_resolv_conf
|
164
|
+
end
|
135
165
|
end
|
136
166
|
|
137
|
-
def
|
138
|
-
|
167
|
+
def resolv_conf
|
168
|
+
File.read('/etc/resolv.conf')
|
139
169
|
end
|
140
170
|
|
141
|
-
def
|
142
|
-
|
171
|
+
def does_hostname_resolve?
|
172
|
+
hostname = `/bin/hostname --fqdn 2>/dev/null`
|
173
|
+
! hostname.empty?
|
174
|
+
end
|
143
175
|
|
144
|
-
|
145
|
-
|
176
|
+
def add_to_search(path, term)
|
177
|
+
data = File.read(path)
|
178
|
+
new_data = data.gsub(/^\s*(search\s+.*)$/) do |match|
|
179
|
+
if match =~ /\b#{term}\b/
|
180
|
+
match
|
181
|
+
else
|
182
|
+
# only actually append the term to the search line if it's not already there.
|
183
|
+
@logger.info "Adding #{term} to the #{path} 'search' line"
|
184
|
+
"#{match} #{term}"
|
185
|
+
end
|
146
186
|
end
|
147
|
-
|
148
|
-
@logger.debug "Writing json dna to file system"
|
149
|
-
update_main_dna(json)
|
150
|
-
json
|
187
|
+
File.open(path,'w') {|fh| fh.write new_data}
|
151
188
|
end
|
152
189
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
190
|
+
def modify_resolv_conf
|
191
|
+
original_resolv_conf = resolv_conf
|
192
|
+
|
193
|
+
add_to_search('/etc/resolv.conf','compute-1.internal')
|
194
|
+
|
195
|
+
if does_hostname_resolve?
|
196
|
+
# if it works, make it survive a dhcp lease renewal
|
197
|
+
add_to_search('/etc/resolv.conf.head', 'compute-1.internal')
|
198
|
+
else
|
199
|
+
@logger.info "Change failed; still can't resolve hostname. Reverting change."
|
200
|
+
File.open('/etc/resolv.conf','w') {|fh| fh.write original_resolv_conf}
|
201
|
+
end
|
158
202
|
end
|
159
203
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
204
|
+
### DNA.json manipulation
|
205
|
+
|
206
|
+
def update_dna
|
207
|
+
FileUtils.mkdir_p @opts[:base_path]
|
208
|
+
file = File.join(@opts[:base_path],'dna.json')
|
209
|
+
|
210
|
+
if File.exists?(file) and not @opts[:refresh_dna] and not @opts[:download]
|
211
|
+
@logger.info "Skipping download of fresh DNA - using existing #{@chef_dna} file."
|
212
|
+
return
|
213
|
+
end
|
214
|
+
|
215
|
+
report "Fetching instance DNA"
|
216
|
+
raise DNAError, "Failed to fetch DNA" unless json = @api.dna
|
217
|
+
|
218
|
+
main_json = {}
|
219
|
+
main_json['run_list'] = @opts[:run_list]
|
220
|
+
main_json['dna'] = json.dup
|
221
|
+
save_dna(file,main_json)
|
166
222
|
end
|
167
223
|
|
168
|
-
def
|
224
|
+
def save_dna(file, json)
|
225
|
+
@logger.debug "Writing json dna to #{file}"
|
169
226
|
File.open(file, 'w') do |f|
|
170
227
|
f.puts JSON.pretty_generate(json)
|
171
228
|
f.chmod(0600)
|
@@ -1,99 +1,175 @@
|
|
1
1
|
module EY::Enzyme
|
2
2
|
class CookbookSet
|
3
|
-
|
4
|
-
|
5
|
-
@
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
3
|
+
|
4
|
+
def initialize(api, opts)
|
5
|
+
@opts = opts
|
6
|
+
@api = api
|
7
|
+
@chef_config = File.join(opts[:base_path], "solo.rb")
|
8
|
+
@chef_dna = File.join(opts[:base_path], "dna.json")
|
9
|
+
@recipes_path = File.join(opts[:base_path], "recipes")
|
10
|
+
@main_recipes_url = opts[:recipes_url]
|
11
|
+
@user_recipes_url = opts.has_key?(:custom_recipes_url) ? opts[:custom_recipes_url] : api.custom_recipe_url
|
12
|
+
@logger = opts[:logger]
|
13
|
+
@timestamp = Time.now.strftime("%Y-%m-%dT%H-%M-%S")
|
14
|
+
@chef_log = "/var/log/chef.#{@timestamp}.log"
|
12
15
|
end
|
13
16
|
|
14
17
|
def run
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
if @main_recipes_url
|
19
|
+
setup
|
20
|
+
execute
|
21
|
+
else
|
22
|
+
@logger.info "No recipes for chef run. Skipping"
|
18
23
|
end
|
19
|
-
|
20
|
-
setup
|
21
|
-
execute
|
22
24
|
ensure
|
23
25
|
upload
|
24
26
|
end
|
25
27
|
|
28
|
+
private
|
29
|
+
|
26
30
|
def setup
|
27
|
-
@logger.info "Starting
|
31
|
+
@logger.info "Starting chef run"
|
28
32
|
|
29
33
|
abort("Instance ID mismatch") unless instance_id_valid?
|
30
34
|
FileUtils.rm(@chef_log) if File.exist?(@chef_log)
|
31
35
|
|
32
|
-
|
36
|
+
# Create the EC2 hint for ohai
|
37
|
+
# See: https://geekblood.wordpress.com/2015/03/13/enabling-ec2-metadata-in-ohai/
|
38
|
+
FileUtils.mkdir_p("#{@opts[:base_path]}/ohai/hints/")
|
39
|
+
FileUtils.touch("#{@opts[:base_path]}/ohai/hints/ec2.json")
|
33
40
|
|
34
|
-
File.open(chef_config, "w") do |f|
|
41
|
+
File.open(@chef_config, "w") do |f|
|
35
42
|
f.puts <<-EOS
|
36
43
|
cookbook_path "#{@recipes_path}/cookbooks"
|
37
44
|
file_store_path "#{@recipes_path}/"
|
38
45
|
file_cache_path "#{@recipes_path}/"
|
46
|
+
data_bag_path "#{@recipes_path}/data_bags"
|
39
47
|
log_location "#{@chef_log}"
|
40
48
|
log_level :info
|
41
49
|
node_name "#{@opts[:instance_id]}"
|
42
50
|
EOS
|
43
51
|
end
|
44
52
|
|
45
|
-
|
53
|
+
if @opts[:download]
|
54
|
+
@logger.info "Removing cookbooks"
|
55
|
+
FileUtils.rm_rf(@recipes_path)
|
56
|
+
FileUtils.mkdir_p(@recipes_path)
|
57
|
+
|
58
|
+
@logger.info "Downloading main cookbooks"
|
59
|
+
download_cookbooks
|
46
60
|
|
47
|
-
|
61
|
+
unless @user_recipes_url.nil?
|
62
|
+
if @opts[:run_custom]
|
63
|
+
@logger.info "Overlaying custom user cookbook recipes"
|
64
|
+
overlay_custom_cookbooks
|
65
|
+
else
|
66
|
+
@logger.info "Skipping overlay of custom cookbook recipes"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@logger.info "Skipping download of new cookbooks - using existing cookbooks in #{@recipes_path}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def download_cookbooks
|
48
75
|
FileUtils.mkdir_p(@recipes_path)
|
76
|
+
result = %x[cd #{@recipes_path} && curl -fs '#{@main_recipes_url}' | tar -zxv]
|
77
|
+
if $?.exitstatus > 0
|
78
|
+
@logger.error "Failed to download and untar main cookbook."
|
79
|
+
@logger.error "Error output: #{result}"
|
80
|
+
raise DeployError, "Main cookbook download failed."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def overlay_custom_cookbooks
|
85
|
+
Dir.mktmpdir do |tmpdir|
|
86
|
+
file=File.join(tmpdir,'user_recipes.tgz')
|
87
|
+
result = %x[curl -fs '#{@user_recipes_url}' -o #{file}]
|
88
|
+
if $?.exitstatus > 0
|
89
|
+
@logger.error "Failed to download user cookbook from #{@user_recipes_url}"
|
90
|
+
raise DeployError, "User cookbook download failed."
|
91
|
+
end
|
92
|
+
|
93
|
+
#Check COMPATIBILITY.txt file for release compatibilities
|
94
|
+
compat = %x[tar -azxf '#{file}' COMPATIBILITY.txt 2>/dev/null]
|
95
|
+
if $?.exitstatus == 0
|
96
|
+
# TODO: Determine how to compare against opts[:release_label]
|
97
|
+
# if not compatibible, log it, and raise DeployError, "Incompatible user cookbook found."
|
98
|
+
else
|
99
|
+
@api.report "WARNING: No COMPATIBILITY.txt file found in user cookbook."
|
100
|
+
end
|
101
|
+
|
102
|
+
result = %x[cd #{@recipes_path} && tar -zxvf #{file}]
|
103
|
+
if $?.exitstatus > 0
|
104
|
+
@logger.error "Failed to overlay user cookbook over main cookbook."
|
105
|
+
@logger.error "Error output: #{result}"
|
106
|
+
raise DeployError, "User cookbook overlay failed."
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
49
110
|
end
|
50
111
|
|
51
112
|
def execute
|
52
|
-
|
113
|
+
# Remove any stacktrace file from previous runs
|
114
|
+
stacktrace_file = File.join(@recipes_path,"chef-stacktrace.out")
|
115
|
+
FileUtils.rm_f stacktrace_file
|
116
|
+
tail = nil
|
117
|
+
if @logger.debug?
|
118
|
+
system("touch #{@chef_log}")
|
119
|
+
tail = Thread.new do
|
120
|
+
File.open(@chef_log, "r") do |fp|
|
121
|
+
while true do
|
122
|
+
select([fp])
|
123
|
+
$stderr << fp.read
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
command = "#{@opts[:chef_bin]} -j #{@chef_dna} -c #{@chef_config} > #{@chef_log} 2>&1"
|
53
129
|
@logger.debug "Running: #{command}"
|
54
130
|
if system(command)
|
55
131
|
@logger.info "Running telinit"
|
56
132
|
system("telinit q")
|
57
|
-
@logger.info "Finished
|
133
|
+
@logger.info "Finished chef run"
|
58
134
|
else
|
59
|
-
@logger.error("
|
60
|
-
@logger.error("
|
61
|
-
raise DeployError, "
|
135
|
+
@logger.error("Chef run failed. Reporting error")
|
136
|
+
@logger.error("Error output: #{chef_error_from_log}")
|
137
|
+
raise DeployError, "Chef run failed - see log file"
|
62
138
|
end
|
63
139
|
ensure
|
64
140
|
if File.exists?(@chef_log)
|
65
|
-
|
141
|
+
if File.exists?(stacktrace_file)
|
142
|
+
st_contents = File.read(stacktrace_file)
|
143
|
+
File.open(@chef_log, "a") do |l|
|
144
|
+
l.puts "\n-----------"
|
145
|
+
l.puts "Stack Trace (from #{stacktrace_file})"
|
146
|
+
l.puts "-----------\n\n#{st_contents}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
FileUtils.ln_sf(@chef_log, "/var/log/chef.log")
|
66
150
|
end
|
151
|
+
tail && tail.kill
|
67
152
|
end
|
68
153
|
|
69
154
|
def upload
|
70
155
|
return unless File.exist?(@chef_log)
|
71
156
|
|
72
157
|
file = "#{@chef_log}.#{rand(1000)}.gz"
|
73
|
-
Zlib::GzipWriter.open(file)
|
158
|
+
Zlib::GzipWriter.open(file) do |io|
|
159
|
+
io << File.read(@chef_log)
|
160
|
+
end
|
74
161
|
|
75
|
-
unless @api.upload(
|
76
|
-
@logger.error "Failed to upload
|
77
|
-
raise UploadError, "
|
162
|
+
unless @api.upload('main', File.new(file))
|
163
|
+
@logger.error "Failed to upload chef log. Reporting error"
|
164
|
+
raise UploadError, "Failed to upload chef log"
|
78
165
|
end
|
79
166
|
rescue UploadError => error
|
167
|
+
@api.report error.message
|
80
168
|
@api.notify_error("logupload", error)
|
81
169
|
ensure
|
82
170
|
FileUtils.rm_f(file) if file
|
83
171
|
end
|
84
172
|
|
85
|
-
def main?
|
86
|
-
@name == "main"
|
87
|
-
end
|
88
|
-
|
89
|
-
def chef_config
|
90
|
-
main? ? "/etc/chef/solo.rb" : "/etc/chef-custom/solo.rb"
|
91
|
-
end
|
92
|
-
|
93
|
-
def chef_dna
|
94
|
-
main? ? '/etc/chef/dna.json' : '/etc/chef-custom/dna.json'
|
95
|
-
end
|
96
|
-
|
97
173
|
def chef_error_from_log
|
98
174
|
unless File.exist?(@chef_log)
|
99
175
|
return "(log doesn't exist)"
|
@@ -122,7 +198,9 @@ module EY::Enzyme
|
|
122
198
|
end
|
123
199
|
|
124
200
|
def local_instance_id
|
125
|
-
@local_instance_id ||= open(
|
201
|
+
@local_instance_id ||= URI.open("http://169.254.169.254/latest/meta-data/instance-id",
|
202
|
+
&:read)
|
126
203
|
end
|
204
|
+
|
127
205
|
end
|
128
206
|
end
|
@@ -1,11 +1,30 @@
|
|
1
1
|
module EY::Enzyme
|
2
2
|
class MultiLogger
|
3
3
|
|
4
|
+
SEVERITIES = {
|
5
|
+
:fatal => Logger::FATAL,
|
6
|
+
:error => Logger::ERROR,
|
7
|
+
:warn => Logger::WARN,
|
8
|
+
:info => Logger::INFO,
|
9
|
+
:debug => Logger::DEBUG,
|
10
|
+
}
|
11
|
+
|
4
12
|
def initialize(log_file)
|
5
13
|
@enzyme_log = Logger.new(log_file)
|
6
14
|
@stderr_log = Logger.new($stderr)
|
7
15
|
end
|
8
16
|
|
17
|
+
def level=(level)
|
18
|
+
@level = level
|
19
|
+
raise "Invalid log level: #{level}" unless SEVERITIES[level]
|
20
|
+
@enzyme_log.level = SEVERITIES[:debug]
|
21
|
+
@stderr_log.level = SEVERITIES[level]
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug?
|
25
|
+
@level == :debug
|
26
|
+
end
|
27
|
+
|
9
28
|
def debug(message)
|
10
29
|
log(:debug, message)
|
11
30
|
end
|
data/lib/ey_enzyme/version.rb
CHANGED
data/lib/ey_enzyme.rb
CHANGED
@@ -3,16 +3,16 @@ require "open-uri"
|
|
3
3
|
require "zlib"
|
4
4
|
require "logger"
|
5
5
|
require "optparse"
|
6
|
+
require "fileutils"
|
6
7
|
|
7
8
|
require 'json'
|
8
9
|
require 'restclient'
|
9
10
|
|
10
11
|
module EY; end
|
12
|
+
require "ey_enzyme/api"
|
11
13
|
|
12
14
|
require "ey_enzyme/multi_logger"
|
13
|
-
require "ey_enzyme/api"
|
14
15
|
require "ey_enzyme/cli"
|
15
|
-
require "ey_enzyme/old_cli"
|
16
16
|
require "ey_enzyme/cookbook_set"
|
17
17
|
require "ey_enzyme/version"
|
18
18
|
|
metadata
CHANGED
@@ -1,160 +1,161 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: ey_enzyme
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 48
|
10
|
-
version: 0.9.48
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.7
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Engine Yard Inc.
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2021-12-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
21
14
|
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.6.1
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.6.0
|
23
|
+
type: :runtime
|
22
24
|
prerelease: false
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.6.1
|
26
30
|
- - ">="
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.6.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mime-types
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 3.3.1
|
32
40
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: rest-client
|
36
41
|
prerelease: false
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.3.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rest-client
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.1'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 2.1.0
|
48
57
|
type: :runtime
|
49
|
-
version_requirements: *id002
|
50
|
-
- !ruby/object:Gem::Dependency
|
51
|
-
name: rake
|
52
58
|
prerelease: false
|
53
|
-
|
54
|
-
|
55
|
-
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '2.1'
|
56
64
|
- - ">="
|
57
|
-
- !ruby/object:Gem::Version
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 2.1.0
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rake
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
62
74
|
type: :development
|
63
|
-
version_requirements: *id003
|
64
|
-
- !ruby/object:Gem::Dependency
|
65
|
-
name: rspec
|
66
75
|
prerelease: false
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.14'
|
77
88
|
type: :development
|
78
|
-
version_requirements: *id004
|
79
|
-
- !ruby/object:Gem::Dependency
|
80
|
-
name: fakeweb
|
81
89
|
prerelease: false
|
82
|
-
|
83
|
-
|
84
|
-
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '2.14'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: fakeweb
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
85
99
|
- - ">="
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
|
88
|
-
segments:
|
89
|
-
- 0
|
90
|
-
version: "0"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
91
102
|
type: :development
|
92
|
-
version_requirements: *id005
|
93
|
-
- !ruby/object:Gem::Dependency
|
94
|
-
name: fakeweb-matcher
|
95
103
|
prerelease: false
|
96
|
-
|
97
|
-
|
98
|
-
requirements:
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
99
106
|
- - ">="
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: fakeweb-matcher
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
105
116
|
type: :development
|
106
|
-
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
107
123
|
description: Gem for kicking off chef recipes
|
108
124
|
email: ninja@engineyard.com
|
109
|
-
executables:
|
125
|
+
executables:
|
110
126
|
- ey-enzyme
|
111
|
-
- ey-recipes
|
112
127
|
extensions: []
|
113
|
-
|
114
128
|
extra_rdoc_files: []
|
115
|
-
|
116
|
-
|
129
|
+
files:
|
130
|
+
- bin/ey-enzyme
|
131
|
+
- lib/ey_enzyme.rb
|
117
132
|
- lib/ey_enzyme/api.rb
|
133
|
+
- lib/ey_enzyme/api/client.rb
|
118
134
|
- lib/ey_enzyme/cli.rb
|
119
135
|
- lib/ey_enzyme/cookbook_set.rb
|
120
136
|
- lib/ey_enzyme/multi_logger.rb
|
121
|
-
- lib/ey_enzyme/old_cli.rb
|
122
137
|
- lib/ey_enzyme/version.rb
|
123
|
-
|
124
|
-
- bin/ey-enzyme
|
125
|
-
- bin/ey-recipes
|
126
|
-
homepage: http://github.com/engineyard/ey_enzyme
|
138
|
+
homepage: https://github.com/engineyard/ey_enzyme
|
127
139
|
licenses: []
|
128
|
-
|
140
|
+
metadata: {}
|
129
141
|
post_install_message:
|
130
142
|
rdoc_options: []
|
131
|
-
|
132
|
-
require_paths:
|
143
|
+
require_paths:
|
133
144
|
- lib
|
134
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
-
|
136
|
-
requirements:
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
137
147
|
- - ">="
|
138
|
-
- !ruby/object:Gem::Version
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
version: "0"
|
143
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
-
none: false
|
145
|
-
requirements:
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
146
152
|
- - ">="
|
147
|
-
- !ruby/object:Gem::Version
|
148
|
-
|
149
|
-
segments:
|
150
|
-
- 0
|
151
|
-
version: "0"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
152
155
|
requirements: []
|
153
|
-
|
154
156
|
rubyforge_project:
|
155
|
-
rubygems_version:
|
157
|
+
rubygems_version: 2.6.14
|
156
158
|
signing_key:
|
157
|
-
specification_version:
|
159
|
+
specification_version: 4
|
158
160
|
summary: Gem for kicking off chef recipes
|
159
161
|
test_files: []
|
160
|
-
|
data/bin/ey-recipes
DELETED
data/lib/ey_enzyme/old_cli.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
module EY::Enzyme
|
2
|
-
class OldCLI
|
3
|
-
def self.run(args)
|
4
|
-
new_args = []
|
5
|
-
|
6
|
-
opts = OptionParser.new do |opts|
|
7
|
-
opts.version = "0.0.1"
|
8
|
-
|
9
|
-
opts.banner = "Usage: ey-recipes[-flag] [argument]"
|
10
|
-
opts.define_head "ey-recipes: running recipes..."
|
11
|
-
opts.separator '*'*80
|
12
|
-
|
13
|
-
opts.on("-c CONFIG", "--config CONFIG", "Use config file") do |config|
|
14
|
-
# NO-OP
|
15
|
-
end
|
16
|
-
|
17
|
-
opts.on("--deploy-main ENV", "Run main recipes") do |env_name|
|
18
|
-
new_args << "--main"
|
19
|
-
end
|
20
|
-
|
21
|
-
opts.on("-d ENV", "--deploy ENV", "Run custom recipes") do |env_name|
|
22
|
-
new_args << "--custom"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
opts.parse!(args)
|
27
|
-
|
28
|
-
if new_args.any?
|
29
|
-
CLI.run(new_args)
|
30
|
-
else
|
31
|
-
$stderr.puts opts
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|