Class: EY::ChefRecipes

Inherits:
Object
  • Object
show all
Defined in:
lib/ey.rb

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ ChefRecipes

Returns a new instance of ChefRecipes.

Raises:

  • (ArgumentError)


18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/ey.rb', line 18

def initialize(opts={})
  raise ArgumentError.new("must provide environment name") unless opts[:env] or opts[:command] == :get_envs
  @opts = opts
  @eyenv = opts[:eyenv] || 'production'
  @env = opts[:env]
  @recipeloc = opts[:recipeloc] || "/etc/chef-custom/recipes"
  @rest = RestClient::Resource.new(opts[:api])
  @keys = {:aws_secret_id => @opts[:aws_secret_id], :aws_secret_key => @opts[:aws_secret_key]}
  @bucket = BucketMinder.new(@opts)
  unless get_envs[@env] or opts[:command] == :get_envs
    puts %Q{#{@env} is not a valid environment name, your available environments are:\n#{get_envs.keys.join("\n")}}
    exit 1
  end  
end

Instance Method Details

#call_api(path, opts = {}) ⇒ Object



33
34
35
36
37
38
# File 'lib/ey.rb', line 33

def call_api(path, opts={})
  JSON.parse(@rest["/api/#{path}"].post(@keys.merge(opts)))
rescue RestClient::RequestFailed
  puts "API call to Engine Yard failed. Are there any running instances for #{@env}"
  exit 1
end

#cleanupObject



232
233
234
# File 'lib/ey.rb', line 232

def cleanup
  @bucket.cleanup
end

#clear_bucketObject



256
257
258
# File 'lib/ey.rb', line 256

def clear_bucket
  @bucket.clear_bucket
end

#convergeObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ey.rb', line 51

def converge
  require 'chef'
  require 'chef/client'
  FileUtils.mkdir_p @recipeloc
  logtype = nil
  build_problem = false
  out = log_to_string do
    begin
      instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").gets
      defaults = {
        :log_level => :info,
        :solo => true,
        :cookbook_path => "#{@recipeloc}/cookbooks",
        :file_store_path => "#{@recipeloc}/",
        :file_cache_path => "#{@recipeloc}/",
        :node_name => instance_id
      }
      Chef::Config.configure { |c| c.merge!(defaults) }
      Chef::Log::Formatter.show_time = false
      Chef::Log.level(Chef::Config[:log_level])
      
      if File.exist?("#{@recipeloc}/cookbooks")
        FileUtils.rm_rf("#{@recipeloc}/cookbooks")
      end
      
      if @opts[:main]
        logtype = "main.logs"
        install_main_recipes
      else
        logtype = "logs"
        install_recipes
      end
      json = get_json(instance_id)
      File.open("/etc/chef/dna.json", 'w'){|f| f.puts JSON.pretty_generate(json)}
      c = Chef::Client.new
      c.json_attribs = json
      c.run_solo
    rescue Object => e
      build_problem = true
      Chef::Log.error(describe_error(e))
    end  
  end
  file = "/tmp/chef-#{rand(1000)}.log"
  File.open(file, 'w'){ |f| f.write out }
  
  @bucket = BucketMinder.new(@opts.merge(:type => logtype, :extension => 'gz'))
  upload_logs(file)
  @bucket.cleanup
  FileUtils.rm(file)
  exit 1 if build_problem
end

#deployObject



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ey.rb', line 118

def deploy
  unless File.exist?("cookbooks")
    puts "you must run this command from the root of your chef recipe git repo"
    exit 1
  end
  env = get_envs[@env]
  unless env['instances'] > 0
    puts "There are no running instances for ENV: #{@env}"
    exit 1
  end
  if upload_recipes
    if env
      puts "deploying recipes..."
      if call_api("deploy_recipes", :id => env['id'] )[0] == 'working'
        wait_for_logs('logs')
      else
        puts "deploying main recipes failed..."
      end
    else
      puts "No matching environments"
    end  
  else
    puts "Failed to deploy: #{@env}"
  end  
end

#deploy_mainObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/ey.rb', line 174

def deploy_main
  env = get_envs[@env]
  if env
    unless env['instances'] > 0
      puts "There are no running instances for ENV: #{@env}"
      exit 1
    end
    puts "deploying main EY recipes..."
    if call_api("deploy_main_recipes", :id => env['id'] )[0] == 'working'
      wait_for_logs('main.logs')
    else
      puts "deploying main recipes failed..."
    end  
  else
    puts "No matching environments"
  end  
end

#describe_error(e) ⇒ Object



260
261
262
# File 'lib/ey.rb', line 260

def describe_error(e)
  "#{e.class.name}: #{e.message}\n #{e.backtrace.join("\n  ")}"
end

#display_logs(obj) ⇒ Object



192
193
194
195
196
197
198
# File 'lib/ey.rb', line 192

def display_logs(obj)
  if obj
    Zlib::GzipReader.new(StringIO.new(obj.value, 'rb')).read
  else
    "no logs..."
  end
end

#download(*args) ⇒ Object



240
241
242
# File 'lib/ey.rb', line 240

def download(*args)
  @bucket.download(*args)
end

#get_currentObject



252
253
254
# File 'lib/ey.rb', line 252

def get_current
  @bucket.get_current
end

#get_envsObject



40
41
42
# File 'lib/ey.rb', line 40

def get_envs
  @_envs ||= call_api("environments")
end

#get_json(instance_id = nil) ⇒ Object



44
45
46
47
48
49
# File 'lib/ey.rb', line 44

def get_json(instance_id = nil)
  env = get_envs[@env]
  call_api("json_for_instance", :id => env['id'], :instance_id => instance_id)
rescue
  {}  
end

#install_main_recipesObject



103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ey.rb', line 103

def install_main_recipes
  unless @opts[:main_recipes]
    puts "you must specify :main_recipes: in your ey-cloud.yml"
    exit 1
  end  
  recipes_path = Chef::Config[:cookbook_path].gsub(/cookbooks/, '')
  FileUtils.mkdir_p recipes_path
  path = File.join(recipes_path, 'recipes.tgz')
  File.open(path, 'wb') do |f|
    f.write open(@opts[:main_recipes]).read
  end
  system("cd #{recipes_path} && tar xzf #{path}")
  FileUtils.rm path
end

#install_recipesObject



244
245
246
247
248
249
250
# File 'lib/ey.rb', line 244

def install_recipes
  file = get_current
  Dir.chdir(@recipeloc) {
    system("tar xzf #{file}")
  }
  FileUtils.rm file
end

#list(*args) ⇒ Object



236
237
238
# File 'lib/ey.rb', line 236

def list(*args)
  @bucket.list(*args)
end

#log_to_string(&block) ⇒ Object



264
265
266
267
268
269
# File 'lib/ey.rb', line 264

def log_to_string(&block)
  output = StringIO.new
  Chef::Log.init(output)
  block.call
  output.string
end

#rollbackObject



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/ey.rb', line 144

def rollback
  @bucket.rollback
  env = get_envs[@env]
  if env
    puts "rolling back recipes..."
    call_api("deploy_recipes", :id => env['id'] )
    wait_for_logs('logs')
  else
    puts "No matching environments for #{@env}"
  end
end

#upload_logs(file) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/ey.rb', line 219

def upload_logs(file)
  name = "#{file}.#{rand(1000)}.tgz"
  tarcmd = "cat #{file} | gzip > #{name}"
  if system(tarcmd)
    @bucket.upload_object(name)
    @bucket.cleanup
    true
  else
    puts "Unable to tar up log files for #{@opts[:env]} wtf?"
    false
  end    
end

#upload_recipesObject



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/ey.rb', line 206

def upload_recipes
  file = "recipes.#{rand(1000)}.tmp.tgz"
  tarcmd = "git archive --format=tar HEAD | gzip > #{file}"
  if system(tarcmd)
    @bucket.upload_object(file)
    @bucket.cleanup
    true
  else
    puts "Unable to tar up recipes for #{@opts[:env]} wtf?"
    false
  end
end

#view_logsObject



200
201
202
203
204
# File 'lib/ey.rb', line 200

def view_logs
  logtype = "#{@opts[:main] ? 'main.' : ''}logs"
  logbucket = BucketMinder.new(@opts.merge(:type => logtype, :extension => 'gz'))
  puts display_logs(logbucket.list.last)
end

#wait_for_logs(logtype) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/ey.rb', line 156

def wait_for_logs(logtype)
  logbucket = BucketMinder.new(@opts.merge(:type => logtype, :extension => 'gz'))
  newest = logbucket.list.last
  count = 0
  until newest != logbucket.list.last
    print "."
    sleep 3
    count += 1
    if count > 600
      puts "timed out waiting for deployed logs"
      exit 1
    end  
  end
  puts
  puts "retrieving logs..."
  puts display_logs(logbucket.list.last)
end