ey_enzyme 0.9.39
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.
- data/bin/ey-enzyme +5 -0
- data/bin/ey-recipes +5 -0
- data/lib/ey_enzyme/api.rb +101 -0
- data/lib/ey_enzyme/cli.rb +173 -0
- data/lib/ey_enzyme/cookbook_set.rb +100 -0
- data/lib/ey_enzyme/multi_logger.rb +46 -0
- data/lib/ey_enzyme/old_cli.rb +35 -0
- data/lib/ey_enzyme/version.rb +5 -0
- data/lib/ey_enzyme.rb +30 -0
- data/spec/api_spec.rb +44 -0
- data/spec/log_upload_error_notify_spec.rb +45 -0
- data/spec/logging_spec.rb +21 -0
- data/spec/retry_report_and_notify_success_spec.rb +62 -0
- data/spec/spec_helper.rb +9 -0
- metadata +234 -0
data/bin/ey-enzyme
ADDED
data/bin/ey-recipes
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module EY::Enzyme
|
2
|
+
class API
|
3
|
+
def initialize(api_url, instance_id, token, log_file = "/var/log/enzyme.log")
|
4
|
+
@api_url = api_url
|
5
|
+
@instance_id = instance_id
|
6
|
+
@token = token
|
7
|
+
@logger = MultiLogger.new(log_file)
|
8
|
+
@rest = RestClient::Resource.new(api_url)
|
9
|
+
end
|
10
|
+
|
11
|
+
def report(message)
|
12
|
+
safe_api("report", :message => message)
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify_success
|
16
|
+
retry_api("completed", :status => 'true')
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_error(type, error)
|
20
|
+
@logger.exception "Notifying #{type} error", error
|
21
|
+
call_api("error", params_for(type, error))
|
22
|
+
rescue RestClient::Exception
|
23
|
+
@logger.exception "Failed to notify of #{type} error", $!
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
|
27
|
+
def dna
|
28
|
+
retry_api("dna")
|
29
|
+
end
|
30
|
+
|
31
|
+
def custom_recipe_url
|
32
|
+
if response = retry_api("custom-recipe")
|
33
|
+
response["url"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#upload, as in, upload the log file output of the chef run
|
38
|
+
def upload(type, content)
|
39
|
+
retry_api("store", :type => type, :content => content)
|
40
|
+
rescue => e
|
41
|
+
#blanket rescue feels dangerous, but we're at least logging it
|
42
|
+
#we don't have all the information we'd like to include in the exception message here anyway
|
43
|
+
#caller (CookbookSet#upload) should check return value and notify failure
|
44
|
+
api_error("store", e)
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def keys
|
51
|
+
@keys ||= {:instance_id => @instance_id, :token => @token}
|
52
|
+
end
|
53
|
+
|
54
|
+
def params_for(type, error)
|
55
|
+
{ :class => error.class,
|
56
|
+
:message => error.message,
|
57
|
+
:backtrace => error.backtrace,
|
58
|
+
:where_failed => type }
|
59
|
+
end
|
60
|
+
|
61
|
+
def call_api(path, opts={})
|
62
|
+
@logger.debug "API call path: #{path.inspect}, data: #{opts.inspect}"
|
63
|
+
JSON.parse(@rest[path].post(opts.merge(keys).to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}))
|
64
|
+
end
|
65
|
+
|
66
|
+
def retry_api(path, opts={})
|
67
|
+
Timeout.timeout(10 * 60) do
|
68
|
+
loop do
|
69
|
+
if result = safe_api(path, opts)
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
sleep 5
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue Timeout::Error
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
def safe_api(path, opts={})
|
80
|
+
begin
|
81
|
+
return call_api(path, opts)
|
82
|
+
rescue RestClient::BadGateway, RestClient::GatewayTimeout
|
83
|
+
api_error(path, $!)
|
84
|
+
return false
|
85
|
+
rescue RestClient::ServiceUnavailable => e
|
86
|
+
response = e.response
|
87
|
+
if response.headers[:content_type] =~ /json/
|
88
|
+
hash = JSON.parse(response)
|
89
|
+
@logger.error "Failed to fetch DNA: #{hash.delete('message')}: #{hash.inspect}"
|
90
|
+
end
|
91
|
+
|
92
|
+
api_error(path, e)
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def api_error(path, error)
|
98
|
+
@logger.exception("API request failed", error)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module EY::Enzyme
|
2
|
+
class CLI
|
3
|
+
IGNORABLE_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt, SystemExit]
|
4
|
+
|
5
|
+
def self.run(args)
|
6
|
+
$stdout.sync = true
|
7
|
+
|
8
|
+
defaults = {:config => '/etc/engineyard/dracul.yml', :keep => 5, :chef_bin => 'chef-solo',
|
9
|
+
:logfile => "/var/log/enzyme.log"}
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
opts = OptionParser.new do |opts|
|
13
|
+
opts.version = EY::Enzyme::VERSION
|
14
|
+
opts.banner = "Usage: ey-enzyme [-flag] [argument]"
|
15
|
+
opts.define_head "ey-enzyme: running recipes..."
|
16
|
+
opts.separator '*'*80
|
17
|
+
|
18
|
+
opts.on("--quick", "Set the recipes to run in quick mode") do
|
19
|
+
options[:quick] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("--logfile", "Set the logfile (defaults to /var/log/enzyme.log)") do |path|
|
23
|
+
options[:logfile] = path
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("--config CONFIG", "Use config file") do |config|
|
27
|
+
options[:config] = config
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("--chef-bin PATH", "Use this chef-solo executable to run recipes") do |binary|
|
31
|
+
options[:chef_bin] = binary
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("--main", "Run main recipes") do
|
35
|
+
options[:type] = :main
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("--custom", "Run custom recipes") do
|
39
|
+
options[:type] = :custom
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("--report message", "Report a status") do |message|
|
43
|
+
options[:report] = true
|
44
|
+
options[:message] = message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.parse!(args)
|
49
|
+
|
50
|
+
config = options[:config] || defaults[:config]
|
51
|
+
cli = new(defaults.merge(YAML.load_file(config)).merge(options))
|
52
|
+
|
53
|
+
if options[:report]
|
54
|
+
cli.report(options[:message])
|
55
|
+
elsif options[:type]
|
56
|
+
cli.deploy
|
57
|
+
else
|
58
|
+
abort opts.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
rescue OptionParser::InvalidOption
|
63
|
+
$stderr.puts "#{File.basename($0)} #{$!.message}"
|
64
|
+
abort opts.to_s
|
65
|
+
rescue *IGNORABLE_EXCEPTIONS
|
66
|
+
raise
|
67
|
+
rescue DeployError
|
68
|
+
if cli
|
69
|
+
cli.notify_user_error($!)
|
70
|
+
exit 0
|
71
|
+
end
|
72
|
+
raise
|
73
|
+
rescue Exception
|
74
|
+
if cli
|
75
|
+
cli.notify_system_error($!)
|
76
|
+
exit 0
|
77
|
+
end
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize(opts={})
|
82
|
+
@opts = opts
|
83
|
+
@api = API.new(opts[:api], opts[:instance_id], opts[:token], opts[:logfile])
|
84
|
+
@logger = MultiLogger.new(opts[:logfile])
|
85
|
+
|
86
|
+
@opts[:timestamp] = Time.now.strftime("%Y-%m-%dT%H-%M-%S")
|
87
|
+
end
|
88
|
+
|
89
|
+
def report(message)
|
90
|
+
@api.report(message)
|
91
|
+
end
|
92
|
+
|
93
|
+
def notify_system_error(error)
|
94
|
+
@api.notify_error("system", error)
|
95
|
+
end
|
96
|
+
|
97
|
+
def notify_user_error(error)
|
98
|
+
@api.notify_error("user", error)
|
99
|
+
end
|
100
|
+
|
101
|
+
MAIN_RECIPE_PATH = '/etc/chef/recipes'
|
102
|
+
CUSTOM_RECIPE_PATH = '/etc/chef-custom/recipes'
|
103
|
+
|
104
|
+
def deploy
|
105
|
+
@logger.info "Starting configuration run"
|
106
|
+
|
107
|
+
update_dna
|
108
|
+
|
109
|
+
case @opts[:type]
|
110
|
+
when :main
|
111
|
+
run_main
|
112
|
+
run_custom
|
113
|
+
when :custom
|
114
|
+
run_custom
|
115
|
+
else
|
116
|
+
raise "Unknown type: #{@opts[:type].inspect}"
|
117
|
+
end
|
118
|
+
|
119
|
+
@api.notify_success
|
120
|
+
end
|
121
|
+
|
122
|
+
def run_main
|
123
|
+
report 'running main recipes'
|
124
|
+
main_cookbooks.run
|
125
|
+
end
|
126
|
+
|
127
|
+
def run_custom
|
128
|
+
report 'running custom recipes if they exist'
|
129
|
+
custom_cookbooks.run
|
130
|
+
end
|
131
|
+
|
132
|
+
def main_cookbooks
|
133
|
+
@main_cookbooks ||= CookbookSet.new(@opts, "main", MAIN_RECIPE_PATH, @opts[:recipes_url], @api)
|
134
|
+
end
|
135
|
+
|
136
|
+
def custom_cookbooks
|
137
|
+
@custom_cookbooks ||= CookbookSet.new(@opts, "custom", CUSTOM_RECIPE_PATH, @api.custom_recipe_url, @api)
|
138
|
+
end
|
139
|
+
|
140
|
+
def update_dna
|
141
|
+
@logger.debug "Getting instance's DNA"
|
142
|
+
|
143
|
+
unless json = @api.dna
|
144
|
+
raise DNAError, "failed to fetch DNA"
|
145
|
+
end
|
146
|
+
|
147
|
+
json["quick"] = true if @opts[:quick]
|
148
|
+
json["removed_applications"] = self.class.find_removed_applications(json)
|
149
|
+
|
150
|
+
@logger.debug "Writing json dna to file system"
|
151
|
+
|
152
|
+
File.open("/etc/chef/dna.json", 'w') do |f|
|
153
|
+
f.puts JSON.pretty_generate(json)
|
154
|
+
f.chmod(0600)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.find_removed_applications(new_dna, file = "/etc/chef/dna.json")
|
159
|
+
json = if file.respond_to?(:string)
|
160
|
+
file.string
|
161
|
+
elsif File.exists?(file)
|
162
|
+
File.read(file)
|
163
|
+
else
|
164
|
+
raise "File Not Found"
|
165
|
+
end
|
166
|
+
|
167
|
+
old_dna = JSON.parse(json)
|
168
|
+
old_dna['applications'].keys - new_dna['applications'].keys
|
169
|
+
rescue
|
170
|
+
[ ]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module EY::Enzyme
|
2
|
+
class CookbookSet
|
3
|
+
def initialize(opts, name, recipes_path, recipes_url, api)
|
4
|
+
@opts = opts
|
5
|
+
@name = name
|
6
|
+
@recipes_path = recipes_path
|
7
|
+
@logger = MultiLogger.new(opts[:logfile])
|
8
|
+
@timestamp = Time.now.strftime("%Y-%m-%dT%H-%M-%S")
|
9
|
+
@chef_log = "/var/log/chef.#{name}.#{@timestamp}.log"
|
10
|
+
@recipes_url = recipes_url
|
11
|
+
@api = api
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
unless @recipes_url
|
16
|
+
@logger.info "No recipes for #{@name} chef run. Skipping"
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
setup
|
21
|
+
execute
|
22
|
+
ensure
|
23
|
+
upload
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup
|
27
|
+
@logger.info "Starting #{@name} chef run"
|
28
|
+
|
29
|
+
abort("Instance ID mismatch") unless instance_id_valid?
|
30
|
+
FileUtils.rm(@chef_log) if File.exist?(@chef_log)
|
31
|
+
|
32
|
+
FileUtils.mkdir_p('/etc/chef-custom/') unless File.exists?('/etc/chef-custom/')
|
33
|
+
|
34
|
+
File.open(chef_config, "w") do |f|
|
35
|
+
f.puts <<-EOS
|
36
|
+
cookbook_path "#{@recipes_path}/cookbooks"
|
37
|
+
file_store_path "#{@recipes_path}/"
|
38
|
+
file_cache_path "#{@recipes_path}/"
|
39
|
+
log_location "#{@chef_log}"
|
40
|
+
log_level :info
|
41
|
+
node_name "#{@opts[:instance_id]}"
|
42
|
+
EOS
|
43
|
+
end
|
44
|
+
|
45
|
+
@logger.info "Removing #{@name} cookbooks"
|
46
|
+
|
47
|
+
FileUtils.rm_rf(@recipes_path)
|
48
|
+
FileUtils.mkdir_p(@recipes_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute
|
52
|
+
command = "#{@opts[:chef_bin]} -j /etc/chef/dna.json -c #{chef_config} -r \"#{@recipes_url}\" > #{@chef_log} 2>&1"
|
53
|
+
@logger.debug "Running: #{command}"
|
54
|
+
if system(command)
|
55
|
+
@logger.info "Running telinit"
|
56
|
+
system("telinit q")
|
57
|
+
@logger.info "Finished #{@name} chef run"
|
58
|
+
else
|
59
|
+
@logger.error("#{@name} chef run failed. Reporting error")
|
60
|
+
raise DeployError, "#{@name} chef run failed"
|
61
|
+
end
|
62
|
+
ensure
|
63
|
+
if File.exists?(@chef_log)
|
64
|
+
FileUtils.ln_sf(@chef_log, "/var/log/chef.#{@name}.log")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def upload
|
69
|
+
return unless File.exist?(@chef_log)
|
70
|
+
|
71
|
+
file = "#{@chef_log}.#{rand(1000)}.gz"
|
72
|
+
Zlib::GzipWriter.open(file) { |io| io << File.read(@chef_log) }
|
73
|
+
|
74
|
+
unless @api.upload(@name, File.read(file))
|
75
|
+
@logger.error "Failed to upload #{type} log. Reporting error"
|
76
|
+
raise UploadError, "failed to upload #{@name} log"
|
77
|
+
end
|
78
|
+
rescue UploadError => error
|
79
|
+
@api.notify_error("logupload", error)
|
80
|
+
ensure
|
81
|
+
FileUtils.rm_f(file) if file
|
82
|
+
end
|
83
|
+
|
84
|
+
def main?
|
85
|
+
@name == "main"
|
86
|
+
end
|
87
|
+
|
88
|
+
def chef_config
|
89
|
+
main? ? "/etc/chef/solo.rb" : "/etc/chef-custom/solo.rb"
|
90
|
+
end
|
91
|
+
|
92
|
+
def instance_id_valid?
|
93
|
+
local_instance_id == @opts[:instance_id]
|
94
|
+
end
|
95
|
+
|
96
|
+
def local_instance_id
|
97
|
+
@local_instance_id ||= open('http://169.254.169.254/latest/meta-data/instance-id').read
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'logglier'
|
2
|
+
|
3
|
+
module EY::Enzyme
|
4
|
+
class MultiLogger
|
5
|
+
|
6
|
+
def self.loggly_url
|
7
|
+
'https://logs.engineyard.loggly.com/inputs/7d877a85-c36a-4e60-a6ba-1d55185e2b91'
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(log_file)
|
11
|
+
@enzyme_log = Logger.new(log_file)
|
12
|
+
@stderr_log = Logger.new($stderr)
|
13
|
+
begin
|
14
|
+
@loggly_log = Logglier.new(self.class.loggly_url, :verify_mode => OpenSSL::SSL::VERIFY_NONE)
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug(message)
|
20
|
+
log(:debug, message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def info(message)
|
24
|
+
log(:info, message)
|
25
|
+
end
|
26
|
+
|
27
|
+
def error(message)
|
28
|
+
log(:error, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def exception(message, e)
|
32
|
+
message = "Got an exception: #{message}: #{e.class}: #{e.message}"
|
33
|
+
error message
|
34
|
+
debug e.backtrace.join(" \n")
|
35
|
+
end
|
36
|
+
|
37
|
+
def log(level, message)
|
38
|
+
@stderr_log.send(level, message)
|
39
|
+
@enzyme_log.send(level, message)
|
40
|
+
begin
|
41
|
+
@loggly_log.send(level, message) if @loggly_log
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
data/lib/ey_enzyme.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "open-uri"
|
3
|
+
require "zlib"
|
4
|
+
require "logger"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
require 'restclient'
|
9
|
+
require 'chef'
|
10
|
+
require 'chef/client'
|
11
|
+
|
12
|
+
module EY; end
|
13
|
+
|
14
|
+
require "ey_enzyme/multi_logger"
|
15
|
+
require "ey_enzyme/api"
|
16
|
+
require "ey_enzyme/cli"
|
17
|
+
require "ey_enzyme/old_cli"
|
18
|
+
require "ey_enzyme/cookbook_set"
|
19
|
+
require "ey_enzyme/version"
|
20
|
+
|
21
|
+
module EY
|
22
|
+
module Enzyme
|
23
|
+
|
24
|
+
class Error < StandardError; end
|
25
|
+
|
26
|
+
class DeployError < Error; end
|
27
|
+
class UploadError < Error; end
|
28
|
+
class DNAError < Error; end
|
29
|
+
end
|
30
|
+
end
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe "enzyme API" do
|
5
|
+
|
6
|
+
before { FakeWeb.allow_net_connect = false }
|
7
|
+
after { FakeWeb.allow_net_connect = true }
|
8
|
+
|
9
|
+
def new_api(*args)
|
10
|
+
old_stderr = $stderr
|
11
|
+
$stderr = StringIO.new
|
12
|
+
api = EY::Enzyme::API.new(*args)
|
13
|
+
$stderr = old_stderr
|
14
|
+
api
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with an EY::Enzyme::API" do
|
18
|
+
before do
|
19
|
+
@enzyme_api = new_api("https://cloud.engineyard.com/dracul",
|
20
|
+
"i-d8babcb5",
|
21
|
+
"8c40f5769f8987489ec66a3abc944123fc0f043c",
|
22
|
+
File.dirname(__FILE__) + '/../tmp/test.log')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to post errors" do
|
26
|
+
FakeWeb.register_uri(
|
27
|
+
:post,
|
28
|
+
"https://cloud.engineyard.com/dracul/error",
|
29
|
+
:body => {}.to_json
|
30
|
+
)
|
31
|
+
|
32
|
+
begin
|
33
|
+
raise "we fail"
|
34
|
+
rescue => e
|
35
|
+
#WE need to do this because the exception is expected to have a backtrace
|
36
|
+
@enzyme_api.notify_error("user", e)
|
37
|
+
end
|
38
|
+
|
39
|
+
FakeWeb.should have_requested(:post, "https://cloud.engineyard.com/dracul/error")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "EY::Enzyme::CookbookSet#upload" do
|
4
|
+
before do
|
5
|
+
FakeWeb.clean_registry
|
6
|
+
@api_url = "https://cloud.engineyard.example.com/dracul"
|
7
|
+
|
8
|
+
fake_cheflog_location = File.expand_path(File.dirname(__FILE__) + '/../tmp/clitestlog')
|
9
|
+
logfile_location = File.expand_path(File.dirname(__FILE__) + '/../tmp/clitestlog')
|
10
|
+
@cookbooks = EY::Enzyme::CLI.new(:logfile => logfile_location, :api => @api_url).main_cookbooks
|
11
|
+
|
12
|
+
#HAX: (might as well be a mock)
|
13
|
+
@cookbooks.instance_eval {
|
14
|
+
@chef_log = fake_cheflog_location
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
it "posts the log file to /store" do
|
19
|
+
FakeWeb.register_uri(
|
20
|
+
:post,
|
21
|
+
@api_url + "/store",
|
22
|
+
:body => {}.to_json
|
23
|
+
)
|
24
|
+
@cookbooks.upload
|
25
|
+
end
|
26
|
+
|
27
|
+
it "posts an error when attempt to upload logs returns 500" do
|
28
|
+
FakeWeb.register_uri(
|
29
|
+
:post,
|
30
|
+
@api_url + "/store",
|
31
|
+
:body => {}.to_json,
|
32
|
+
:status => ["500", "Internal Server Error"]
|
33
|
+
)
|
34
|
+
FakeWeb.register_uri(
|
35
|
+
:post,
|
36
|
+
@api_url + "/error",
|
37
|
+
:body => {}.to_json
|
38
|
+
)
|
39
|
+
@cookbooks.upload
|
40
|
+
posted = JSON.parse(FakeWeb.last_request.body.to_s)
|
41
|
+
posted["where_failed"].should == "logupload"
|
42
|
+
posted["message"].should == "failed to upload main log"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe EY::Enzyme::MultiLogger do
|
4
|
+
|
5
|
+
before do
|
6
|
+
FakeWeb.clean_registry
|
7
|
+
FakeWeb.allow_net_connect = false
|
8
|
+
end
|
9
|
+
|
10
|
+
it "it should have a loggly_url" do
|
11
|
+
EY::Enzyme::MultiLogger.loggly_url.should_not == nil
|
12
|
+
EY::Enzyme::MultiLogger.loggly_url.should == 'https://logs.engineyard.loggly.com/inputs/7d877a85-c36a-4e60-a6ba-1d55185e2b91'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "it should log something to loggly" do
|
16
|
+
FakeWeb.register_uri(:post, EY::Enzyme::MultiLogger.loggly_url, :body => {}.to_json)
|
17
|
+
logger = EY::Enzyme::MultiLogger.new($stdout)
|
18
|
+
logger.debug("test")
|
19
|
+
FakeWeb.should have_requested(:post, EY::Enzyme::MultiLogger.loggly_url)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "EY::Enzyme::API" do
|
4
|
+
before do
|
5
|
+
FakeWeb.clean_registry
|
6
|
+
@api_url = "https://cloud.engineyard.example.com/dracul"
|
7
|
+
|
8
|
+
logfile_location = File.expand_path(File.dirname(__FILE__) + '/../tmp/clitestlog')
|
9
|
+
@cli = EY::Enzyme::CLI.new(:logfile => logfile_location, :api => @api_url)
|
10
|
+
@api = @cli.instance_eval{ @api }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "notify_success" do
|
14
|
+
it "can notify success" do
|
15
|
+
FakeWeb.register_uri(
|
16
|
+
:post,
|
17
|
+
@api_url + "/completed",
|
18
|
+
:body => {}.to_json
|
19
|
+
)
|
20
|
+
@api.notify_success
|
21
|
+
end
|
22
|
+
|
23
|
+
it "retries on 502" do
|
24
|
+
FakeWeb.register_uri(
|
25
|
+
:post,
|
26
|
+
@api_url + "/completed",
|
27
|
+
[
|
28
|
+
{:body => {}.to_json, :status => ["502", "Bad Gateway"]},
|
29
|
+
{:body => {}.to_json, :status => ["200", "Success"]},
|
30
|
+
]
|
31
|
+
)
|
32
|
+
lambda{ @api.notify_success }.should_not raise_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "report" do
|
37
|
+
it "can report chef statuses" do
|
38
|
+
FakeWeb.register_uri(
|
39
|
+
:post,
|
40
|
+
@api_url + "/report",
|
41
|
+
:body => {}.to_json
|
42
|
+
)
|
43
|
+
@api.report 'report some status'
|
44
|
+
|
45
|
+
posted = JSON.parse(FakeWeb.last_request.body.to_s)
|
46
|
+
posted["message"].should == "report some status"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't raise or retry on 502" do
|
50
|
+
FakeWeb.register_uri(
|
51
|
+
:post,
|
52
|
+
@api_url + "/report",
|
53
|
+
[
|
54
|
+
{:body => {}.to_json, :status => ["502", "Bad Gateway"]},
|
55
|
+
{:body => lambda{ raise "no!" }}, #bad bad hax, doesn't work the way you would hope
|
56
|
+
]
|
57
|
+
)
|
58
|
+
lambda{ @api.report 'some status update' }.should_not raise_error
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'ey_enzyme'
|
4
|
+
require 'fakeweb'
|
5
|
+
require 'fakeweb_matcher'
|
6
|
+
|
7
|
+
tmpdir = File.expand_path(File.dirname(__FILE__) + '/../tmp')
|
8
|
+
FileUtils.mkdir_p tmpdir
|
9
|
+
FileUtils.rm Dir.glob("#{tmpdir}/*")
|
metadata
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ey_enzyme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 117
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 39
|
10
|
+
version: 0.9.39
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Engine Yard Inc.
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-02-25 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rest-client
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 13
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 6
|
47
|
+
- 1
|
48
|
+
version: 1.6.1
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: chef
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - "="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 123
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
- 6
|
63
|
+
- 0
|
64
|
+
- 2
|
65
|
+
version: 0.6.0.2
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: chef-deploy
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - "="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 29
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
- 2
|
80
|
+
- 5
|
81
|
+
version: 0.2.5
|
82
|
+
type: :runtime
|
83
|
+
version_requirements: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: logglier
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 29
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
- 2
|
96
|
+
- 5
|
97
|
+
version: 0.2.5
|
98
|
+
type: :runtime
|
99
|
+
version_requirements: *id005
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: rake
|
102
|
+
prerelease: false
|
103
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
type: :development
|
113
|
+
version_requirements: *id006
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: rspec
|
116
|
+
prerelease: false
|
117
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ~>
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 11
|
123
|
+
segments:
|
124
|
+
- 1
|
125
|
+
- 2
|
126
|
+
version: "1.2"
|
127
|
+
type: :development
|
128
|
+
version_requirements: *id007
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: ruby-debug
|
131
|
+
prerelease: false
|
132
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
type: :development
|
142
|
+
version_requirements: *id008
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: fakeweb
|
145
|
+
prerelease: false
|
146
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 3
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
type: :development
|
156
|
+
version_requirements: *id009
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: fakeweb-matcher
|
159
|
+
prerelease: false
|
160
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
hash: 3
|
166
|
+
segments:
|
167
|
+
- 0
|
168
|
+
version: "0"
|
169
|
+
type: :development
|
170
|
+
version_requirements: *id010
|
171
|
+
description: Gem for kicking off chef recipes
|
172
|
+
email: ninja@engineyard.com
|
173
|
+
executables:
|
174
|
+
- ey-enzyme
|
175
|
+
- ey-recipes
|
176
|
+
extensions: []
|
177
|
+
|
178
|
+
extra_rdoc_files: []
|
179
|
+
|
180
|
+
files:
|
181
|
+
- lib/ey_enzyme/api.rb
|
182
|
+
- lib/ey_enzyme/cli.rb
|
183
|
+
- lib/ey_enzyme/cookbook_set.rb
|
184
|
+
- lib/ey_enzyme/multi_logger.rb
|
185
|
+
- lib/ey_enzyme/old_cli.rb
|
186
|
+
- lib/ey_enzyme/version.rb
|
187
|
+
- lib/ey_enzyme.rb
|
188
|
+
- spec/api_spec.rb
|
189
|
+
- spec/log_upload_error_notify_spec.rb
|
190
|
+
- spec/logging_spec.rb
|
191
|
+
- spec/retry_report_and_notify_success_spec.rb
|
192
|
+
- spec/spec_helper.rb
|
193
|
+
- bin/ey-enzyme
|
194
|
+
- bin/ey-recipes
|
195
|
+
has_rdoc: true
|
196
|
+
homepage: http://github.com/engineyard/ey_enzyme
|
197
|
+
licenses: []
|
198
|
+
|
199
|
+
post_install_message:
|
200
|
+
rdoc_options: []
|
201
|
+
|
202
|
+
require_paths:
|
203
|
+
- lib
|
204
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
205
|
+
none: false
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
hash: 3
|
210
|
+
segments:
|
211
|
+
- 0
|
212
|
+
version: "0"
|
213
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
214
|
+
none: false
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
hash: 3
|
219
|
+
segments:
|
220
|
+
- 0
|
221
|
+
version: "0"
|
222
|
+
requirements: []
|
223
|
+
|
224
|
+
rubyforge_project:
|
225
|
+
rubygems_version: 1.6.2
|
226
|
+
signing_key:
|
227
|
+
specification_version: 3
|
228
|
+
summary: Gem for kicking off chef recipes
|
229
|
+
test_files:
|
230
|
+
- spec/api_spec.rb
|
231
|
+
- spec/log_upload_error_notify_spec.rb
|
232
|
+
- spec/logging_spec.rb
|
233
|
+
- spec/retry_report_and_notify_success_spec.rb
|
234
|
+
- spec/spec_helper.rb
|