ey_enzyme 0.9.39

Sign up to get free protection for your applications and to get access to all the features.
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