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 ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ey_enzyme'
4
+
5
+ EY::Enzyme::CLI.run(ARGV)
data/bin/ey-recipes ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ey_enzyme'
4
+
5
+ EY::Enzyme::OldCLI.run(ARGV)
@@ -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
@@ -0,0 +1,5 @@
1
+ module EY
2
+ module Enzyme
3
+ VERSION = "0.9.39"
4
+ end
5
+ 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
@@ -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