Merge pull request #404 from cantino/heroku

Heroku deployment!

Andrew Cantino 10 years ago
parent
commit
fcc97dd683

+ 2 - 0
.buildpacks

@@ -0,0 +1,2 @@
1
+https://github.com/cantino/heroku-selectable-procfile.git
2
+https://github.com/heroku/heroku-buildpack-ruby.git

+ 14 - 3
Gemfile

@@ -74,12 +74,9 @@ gem 'hipchat', '~> 1.2.0'
74 74
 gem 'xmpp4r',  '~> 0.5.6'
75 75
 gem 'feed-normalizer'
76 76
 gem 'slack-notifier', '~> 0.5.0'
77
-
78 77
 gem 'therubyracer', '~> 0.12.1'
79
-
80 78
 gem 'mqtt'
81 79
 
82
-
83 80
 group :development do
84 81
   gem 'binding_of_caller'
85 82
   gem 'better_errors'
@@ -103,3 +100,17 @@ group :production do
103 100
   gem 'dotenv-deployment'
104 101
   gem 'rack'
105 102
 end
103
+
104
+# This hack needs some explanation.  When on Heroku, use the pg, unicorn, and rails12factor gems.
105
+# When not on Heroku, we still want our Gemfile.lock to include these gems, so we scope them to
106
+# an unsupported platform.
107
+if ENV['ON_HEROKU'] || ENV['HEROKU_POSTGRESQL_ROSE_URL'] || File.read(File.join(File.dirname(__FILE__), 'Procfile')) =~ /intended for Heroku/
108
+  gem 'pg'
109
+  gem 'unicorn'
110
+  gem 'rails_12factor'
111
+else
112
+  gem 'pg', platform: :ruby_18
113
+  gem 'unicorn', platform: :ruby_18
114
+  gem 'rails_12factor', platform: :ruby_18
115
+end
116
+

+ 15 - 0
Gemfile.lock

@@ -165,6 +165,7 @@ GEM
165 165
     kaminari (0.16.1)
166 166
       actionpack (>= 3.0.0)
167 167
       activesupport (>= 3.0.0)
168
+    kgio (2.9.2)
168 169
     kramdown (1.3.3)
169 170
     launchy (2.4.2)
170 171
       addressable (~> 2.3)
@@ -196,6 +197,7 @@ GEM
196 197
       multi_xml (~> 0.5)
197 198
       rack (~> 1.2)
198 199
     orm_adapter (0.5.0)
200
+    pg (0.17.1)
199 201
     polyglot (0.3.5)
200 202
     protected_attributes (1.0.8)
201 203
       activemodel (>= 4.0.1, < 5.0)
@@ -218,11 +220,17 @@ GEM
218 220
       bundler (>= 1.3.0, < 2.0)
219 221
       railties (= 4.1.4)
220 222
       sprockets-rails (~> 2.0)
223
+    rails_12factor (0.0.2)
224
+      rails_serve_static_assets
225
+      rails_stdout_logging
226
+    rails_serve_static_assets (0.0.2)
227
+    rails_stdout_logging (0.0.3)
221 228
     railties (4.1.4)
222 229
       actionpack (= 4.1.4)
223 230
       activesupport (= 4.1.4)
224 231
       rake (>= 0.8.7)
225 232
       thor (>= 0.18.1, < 2.0)
233
+    raindrops (0.13.0)
226 234
     rake (10.3.2)
227 235
     ref (1.0.5)
228 236
     rest-client (1.6.7)
@@ -322,6 +330,10 @@ GEM
322 330
     uglifier (2.5.1)
323 331
       execjs (>= 0.3.0)
324 332
       json (>= 1.8.0)
333
+    unicorn (4.8.3)
334
+      kgio (~> 2.6)
335
+      rack
336
+      raindrops (~> 0.7)
325 337
     uuid (2.3.7)
326 338
       macaddr (~> 1.0)
327 339
     uuidtools (2.1.4)
@@ -380,11 +392,13 @@ DEPENDENCIES
380 392
   mqtt
381 393
   mysql2 (~> 0.3.16)
382 394
   nokogiri (~> 1.6.1)
395
+  pg
383 396
   protected_attributes (~> 1.0.8)
384 397
   pry
385 398
   quiet_assets
386 399
   rack
387 400
   rails (= 4.1.4)
401
+  rails_12factor
388 402
   rr
389 403
   rspec (~> 2.14)
390 404
   rspec-rails (~> 2.14)
@@ -401,6 +415,7 @@ DEPENDENCIES
401 415
   typhoeus (~> 0.6.3)
402 416
   tzinfo-data
403 417
   uglifier (>= 1.3.0)
418
+  unicorn
404 419
   vcr
405 420
   webmock (~> 1.17.4)
406 421
   weibo_2 (~> 0.1.4)

+ 5 - 5
Procfile

@@ -6,8 +6,8 @@ jobs: bundle exec rails runner bin/threaded.rb
6 6
 # web: bundle exec unicorn -c config/unicorn/production.rb
7 7
 # jobs: bundle exec rails runner bin/threaded.rb
8 8
 
9
-# Old version with seperate processes (use this if you have issues with the threaded version)
10
-#web: bundle exec rails server
11
-#schedule: bundle exec rails runner bin/schedule.rb
12
-#twitter: bundle exec rails runner bin/twitter_stream.rb
13
-#dj: bundle exec script/delayed_job run
9
+# Old version with separate processes (use this if you have issues with the threaded version)
10
+# web: bundle exec rails server
11
+# schedule: bundle exec rails runner bin/schedule.rb
12
+# twitter: bundle exec rails runner bin/twitter_stream.rb
13
+# dj: bundle exec script/delayed_job run

+ 1 - 1
app/models/agents/peak_detector_agent.rb

@@ -38,7 +38,7 @@ module Agents
38 38
         'expected_receive_period_in_days' => "2",
39 39
         'group_by_path' => "filter",
40 40
         'value_path' => "count",
41
-        'message' => "A peak was found"
41
+        'message' => "A peak of {{count}} was found in {{filter}}"
42 42
       }
43 43
     end
44 44
 

+ 160 - 0
bin/setup_heroku

@@ -0,0 +1,160 @@
1
+#!/usr/bin/env ruby
2
+require 'open3'
3
+require 'io/console'
4
+
5
+unless `which heroku` =~ /heroku/
6
+  puts "It looks like the heroku command line tool hasn't been installed yet.  Please install"
7
+  puts "the Heroku Toolbelt from https://toolbelt.heroku.com, run 'heroku auth:login', and then"
8
+  puts "run this script again."
9
+  exit 1
10
+end
11
+
12
+def capture(cmd, opts = {})
13
+  o, s = Open3.capture2e(cmd, opts)
14
+  o.strip
15
+end
16
+
17
+def ask(question, opts = {})
18
+  print question + " "
19
+  STDOUT.flush
20
+  (opts[:noecho] ? STDIN.noecho(&:gets) : gets).strip
21
+end
22
+
23
+def nag(question, opts = {})
24
+  answer = ''
25
+  while answer.length == 0
26
+    answer = ask(question, opts)
27
+  end
28
+  answer
29
+end
30
+
31
+def yes?(question)
32
+  ask(question + " (y/n)") =~ /^y/i
33
+end
34
+
35
+def grab_heroku_config!
36
+  config_data = capture("heroku config -s")
37
+  $config = {}
38
+  if config_data !~ /has no config vars/
39
+    config_data.split("\n").map do |line|
40
+      next if line =~ /^\s*(#|$)/ # skip comments and empty lines
41
+      first_equal_sign = line.index('=')
42
+      $config[line.slice(0, first_equal_sign)] = line.slice(first_equal_sign + 1, line.length)
43
+    end
44
+  end
45
+end
46
+
47
+def set_value(key, value, options = {})
48
+  if $config[key].nil? || $config[key] == '' || ($config[key] != value && options[:force] != false)
49
+    puts "Setting #{key} to #{value}" unless options[:silent]
50
+    puts capture("heroku config:set #{key}=#{value}")
51
+  end
52
+end
53
+
54
+unless File.exists?(File.expand_path("~/.netrc")) && File.read(File.expand_path("~/.netrc")) =~ /heroku/
55
+  puts "It looks like you need to log in to Heroku.  Please run 'heroku auth:login' before continuing."
56
+  exit 1
57
+end
58
+
59
+puts "Welcome #{`heroku auth:whoami`.strip}!  It looks like you're logged into Heroku."
60
+puts
61
+
62
+info = capture("heroku info")
63
+if info =~ /No app specified/i
64
+  puts "It looks like you don't have a Heroku app set up yet for this repo."
65
+  puts "You can either exit now and run 'heroku create', or I can do it for you."
66
+  if yes?("Would you like me to create a Heroku app for you now in this repo?")
67
+    puts `heroku create`
68
+    info = capture("heroku info")
69
+  else
70
+    puts "Okay, exiting so you can do it."
71
+    exit 0
72
+  end
73
+end
74
+
75
+app_name = info.scan(/http:\/\/([\w\d-]+)\.herokuapp\.com/).flatten.first
76
+
77
+unless yes?("Your Heroku app name is #{app_name}.  Is this correct?")
78
+  puts "Well, then I'm not sure what to do here, sorry."
79
+  exit 1
80
+end
81
+
82
+grab_heroku_config!
83
+
84
+if $config.length > 0
85
+  puts
86
+  puts "Your current Heroku config:"
87
+  $config.each do |key, value|
88
+    puts '  ' + key + ' ' * (25 - [key.length, 25].min) + '= ' + value
89
+  end
90
+end
91
+
92
+unless $config['APP_SECRET_TOKEN']
93
+  puts "Setting up APP_SECRET_TOKEN..."
94
+  puts capture("heroku config:set APP_SECRET_TOKEN=`rake secret`")
95
+end
96
+
97
+set_value 'BUILDPACK_URL', "https://github.com/ddollar/heroku-buildpack-multi.git"
98
+set_value 'PROCFILE_PATH', "deployment/heroku/Procfile.heroku", force: false
99
+set_value 'ON_HEROKU', "true"
100
+set_value 'FORCE_SSL', "true"
101
+set_value 'DOMAIN', "#{app_name}.herokuapp.com", force: false
102
+
103
+unless $config['INVITATION_CODE']
104
+  puts "You need to set an invitation code for your Huginn instance.  If you plan to share this instance, you will"
105
+  puts "tell this code to anyone who you'd like to invite.  If you won't share it, then just set this to something"
106
+  puts "that people will not guess."
107
+
108
+  invitation_code = nag("What code would you like to use?")
109
+  set_value 'INVITATION_CODE', invitation_code
110
+end
111
+
112
+unless $config['SMTP_DOMAIN'] && $config['SMTP_USER_NAME'] && $config['SMTP_PASSWORD'] && $config['SMTP_SERVER'] && $config['EMAIL_FROM_ADDRESS']
113
+  puts "Okay, let's setup outgoing email settings.  The simplest solution is to use the free sendgrid Heroku addon."
114
+  puts "If you'd like to use your own server, or your Gmail account, please see .env.example and set"
115
+  puts "SMTP_DOMAIN, SMTP_USER_NAME, SMTP_PASSWORD, and SMTP_SERVER with 'heroku config:set'."
116
+  if yes?("Should I enable the free sendgrid addon?")
117
+    puts capture("heroku addons:add sendgrid")
118
+
119
+    set_value 'SMTP_SERVER', "smtp.sendgrid.net", silent: true
120
+    set_value 'SMTP_DOMAIN', "heroku.com", silent: true
121
+
122
+    grab_heroku_config!
123
+    set_value 'SMTP_USER_NAME', $config['SENDGRID_USERNAME'], silent: true
124
+    set_value 'SMTP_PASSWORD', $config['SENDGRID_PASSWORD'], silent: true
125
+  else
126
+    puts "Okay, you'll need to set SMTP_DOMAIN, SMTP_USER_NAME, SMTP_PASSWORD, and SMTP_SERVER with 'heroku config:set' manually."
127
+  end
128
+
129
+  unless $config['EMAIL_FROM_ADDRESS']
130
+    email = nag("What email address would you like email to appear to be sent from?")
131
+    set_value 'EMAIL_FROM_ADDRESS', email
132
+  end
133
+end
134
+
135
+branch = capture("git rev-parse --abbrev-ref HEAD")
136
+if yes?("Should I push your current branch (#{branch}) to heroku?")
137
+  puts "This may take a moment..."
138
+  puts capture("git push heroku #{branch}:master -f")
139
+
140
+  puts "Running database migrations..."
141
+  puts capture("heroku run rake db:migrate")
142
+
143
+  puts
144
+  puts
145
+  puts "I can make an admin user on your new Huginn instance and setup some example Agents."
146
+  if yes?("Should I create a new admin user and some example Agents?")
147
+    seed_email = nag "Okay, what is your email address?"
148
+    seed_username = nag "And what username would you like to login as?"
149
+    seed_password = nag "Finally, what password would you like to use?", noecho: true
150
+    puts "\nJust a moment..."
151
+
152
+    capture("heroku run rake db:seed SEED_EMAIL=#{seed_email} SEED_USERNAME=#{seed_username} SEED_PASSWORD=#{seed_password}")
153
+    puts
154
+    puts
155
+    puts "Okay, you should be all set!  Visit https://#{app_name}.herokuapp.com and login as '#{seed_username}' with your password."
156
+  end
157
+end
158
+
159
+puts
160
+puts "Done!"

+ 2 - 2
config/initializers/delayed_job.rb

@@ -5,5 +5,5 @@ Delayed::Worker.read_ahead = 5
5 5
 Delayed::Worker.default_priority = 10
6 6
 Delayed::Worker.delay_jobs = !Rails.env.test?
7 7
 
8
-Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log'))
9
-Delayed::Worker.logger.level = Logger::DEBUG
8
+# Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log'))
9
+# Delayed::Worker.logger.level = Logger::DEBUG

+ 4 - 4
db/seeds.rb

@@ -1,10 +1,10 @@
1 1
 # This file should contain all the record creation needed to seed the database with its default values.
2 2
 # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 3
 
4
-user = User.find_or_initialize_by(:email => "admin@example.com")
5
-user.username = "admin"
6
-user.password = "password"
7
-user.password_confirmation = "password"
4
+user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'] || "admin@example.com")
5
+user.username = ENV['SEED_USERNAME'] || "admin"
6
+user.password = ENV['SEED_PASSWORD'] || "password"
7
+user.password_confirmation = ENV['SEED_PASSWORD'] || "password"
8 8
 user.invitation_code = User::INVITATION_CODES.first
9 9
 user.admin = true
10 10
 user.save!

+ 4 - 0
deployment/heroku/Procfile.heroku

@@ -0,0 +1,4 @@
1
+# This Procfile is intended for Heroku, and is detected by the Gemfile.  DO NOT REMOVE THIS LINE!
2
+
3
+# deployment/heroku/unicorn.rb is a special Unicorn config file that also spawns workers.
4
+web: bundle exec unicorn -p $PORT -c ./deployment/heroku/unicorn.rb

+ 51 - 0
deployment/heroku/unicorn.rb

@@ -0,0 +1,51 @@
1
+require "net/http"
2
+
3
+worker_processes Integer(ENV["WEB_CONCURRENCY"] || 2)
4
+timeout 15
5
+preload_app true
6
+
7
+# Note that this will only work correctly when running Heroku with ONE web worker.
8
+# If you want to run more than one, use the standard Huginn Procfile instead, with separate web and job entries.
9
+# You'll need to set the Heroku config variable PROCFILE_PATH to 'Procfile'.
10
+Thread.new do
11
+  worker_pid = nil
12
+  while true
13
+    if worker_pid.nil?
14
+      worker_pid = spawn("bundle exec rails runner bin/threaded.rb")
15
+      puts "New threaded worker PID: #{worker_pid}"
16
+    end
17
+
18
+    sleep 45
19
+
20
+    if ENV['DOMAIN']
21
+      force_ssl = ENV['FORCE_SSL'].present? && ENV['FORCE_SSL'] == 'true'
22
+      Net::HTTP.get_response(URI((force_ssl ? "https://" : "http://") + ENV['DOMAIN']))
23
+    end
24
+
25
+    begin
26
+      Process.getpgid worker_pid
27
+    rescue Errno::ESRCH
28
+      # No longer running
29
+      worker_pid = nil
30
+    end
31
+  end
32
+end
33
+
34
+before_fork do |server, worker|
35
+  Signal.trap 'TERM' do
36
+    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
37
+    Process.kill 'QUIT', Process.pid
38
+  end
39
+
40
+  defined?(ActiveRecord::Base) and
41
+    ActiveRecord::Base.connection.disconnect!
42
+end
43
+
44
+after_fork do |server, worker|
45
+  Signal.trap 'TERM' do
46
+    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
47
+  end
48
+
49
+  defined?(ActiveRecord::Base) and
50
+    ActiveRecord::Base.establish_connection
51
+end