heroku_san 1.3.0 → 2.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,6 +3,6 @@ doc
3
3
  pkg
4
4
  Thumbs.db
5
5
  tmp
6
-
6
+ .idea/**
7
7
  Gemfile.lock
8
8
  *.swp
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm @heroku_san --create
1
+ rvm gemset use heroku_san --create
data/README.rdoc CHANGED
@@ -121,7 +121,8 @@ Issue Tracker:: http://github.com/fastestforward/heroku_san/issues
121
121
  * Jon Wood (jon@blankpad.net)
122
122
  * Mat Schaffer (mat@schaffer.me)
123
123
  * Jonathan Hironaga (jonathan.hironaga@halogennetwork.com)
124
+ * Ken Mayer (ken@bitwrangler.com)
124
125
 
125
126
  == License
126
127
 
127
- License:: Copyright (c) 2009 Elijah Miller <mailto:elijah.miller@gmail.com>, released under the MIT license.
128
+ License:: Copyright (c) 2009-2012 Elijah Miller <mailto:elijah.miller@gmail.com>, released under the MIT license.
data/Rakefile CHANGED
@@ -1,5 +1,13 @@
1
1
  require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- desc 'Default: build gem.'
5
- task :default => :build
6
+ desc 'Default: run unit tests.'
7
+ task :default => :spec
8
+
9
+ desc "Run all specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = 'spec/**/*_spec.rb'
12
+ t.rspec_opts = ["-c", "-f progress"]
13
+ end
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/cucumber.yml ADDED
@@ -0,0 +1,2 @@
1
+ default: --tags ~@slow_process
2
+ remote: --tags @slow_process
@@ -0,0 +1,62 @@
1
+ # Support for auto_tagger (0.2.6) (http://github.com/zilkey/auto_tagger)
2
+
3
+ # Add the following to your ci build script
4
+ # this will create a tag if the build is successful
5
+ # rake ci-test-target autotag:create[ci]
6
+
7
+ # Add the following to your heroku.yml file:
8
+ # staging:
9
+ # tag: ci/*
10
+ #
11
+ # production:
12
+ # tag: staging/*
13
+
14
+ # Builds will be deployed in the following sequence:
15
+ # 1. ci passes, creates ci tag from HEAD
16
+ # 2. deploy latest ci tag to staging
17
+ # 3. deploy latest staging tag to production
18
+
19
+ STAGES = %w[ci staging production]
20
+
21
+ def create_and_push(stage)
22
+ auto_tag = AutoTagger::Base.new(stages: STAGES, stage: stage, verbose: true, push_refs: false, refs_to_keep: 100)
23
+ tag = auto_tag.create_ref(auto_tag.last_ref_from_previous_stage.try(:sha))
24
+ sh "git push origin #{tag.name}"
25
+ auto_tag.delete_locally
26
+ auto_tag.delete_on_remote
27
+ end
28
+
29
+ task :before_deploy do
30
+ sh "git fetch --tags"
31
+ end
32
+
33
+ task :after_deploy do
34
+ each_heroku_app do |stage|
35
+ create_and_push(stage.name)
36
+ end
37
+ Rake::Task['autotag:list'].invoke
38
+ end
39
+
40
+ namespace :autotag do
41
+ desc "Create an autotag for stage, default: #{STAGES.first}"
42
+ task :create, :stage do |t, args|
43
+ create_and_push(args[:stage] || STAGES.first)
44
+ end
45
+
46
+ desc "Show auto tagger history"
47
+ task :list do
48
+ puts "** AUTO TAGGER: release tag history:"
49
+ auto_tag = AutoTagger::Base.new(stages: STAGES, offline: true)
50
+ STAGES.each do |stage|
51
+ ref = auto_tag.refs_for_stage(stage).last
52
+ if ref
53
+ log = %x{git log -1 --format=oneline #{ref.sha}}.chomp
54
+ log = log[0..5] + log[40..-1]
55
+ else
56
+ log = "none"
57
+ end
58
+ puts " ** %-12s %s" % [stage, log]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,6 +1,6 @@
1
1
  Feature: Command Line
2
2
 
3
- Scenario: Config file can be formatted like Rails' database.yml
3
+ Background:
4
4
  Given I run `rails new heroku_san_test -O`
5
5
  And I cd to "heroku_san_test"
6
6
  And I overwrite "Gemfile" with:
@@ -8,6 +8,8 @@ Feature: Command Line
8
8
  source :rubygems
9
9
  gem 'heroku_san', :path => '../../../.'
10
10
  """
11
+
12
+ Scenario: Config file can be formatted like Rails' database.yml
11
13
  Given a file named "config/heroku.yml" with:
12
14
  """
13
15
  production:
@@ -18,20 +20,13 @@ Feature: Command Line
18
20
  app: awesomeapp-demo
19
21
  """
20
22
 
21
- When I run `rake heroku:apps:local`
23
+ When I run `rake --trace heroku:apps:local`
22
24
 
23
25
  Then the output should contain "production is shorthand for the Heroku app awesomeapp"
24
26
  And the output should contain "staging is shorthand for the Heroku app awesomeapp-staging"
25
27
  And the output should contain "demo is shorthand for the Heroku app awesomeapp-demo"
26
28
 
27
29
  Scenario: Config file still accepts the heroku_san format
28
- Given I run `rails new heroku_san_test -O`
29
- And I cd to "heroku_san_test"
30
- And I overwrite "Gemfile" with:
31
- """
32
- source :rubygems
33
- gem 'heroku_san', :path => '../../../.'
34
- """
35
30
  Given a file named "config/heroku.yml" with:
36
31
  """
37
32
  apps:
@@ -40,8 +35,37 @@ Feature: Command Line
40
35
  demo: awesomeapp-demo
41
36
  """
42
37
 
43
- When I run `rake heroku:apps:local`
38
+ When I run `rake --trace heroku:apps:local`
44
39
 
45
40
  Then the output should contain "production is shorthand for the Heroku app awesomeapp"
46
41
  And the output should contain "staging is shorthand for the Heroku app awesomeapp-staging"
47
42
  And the output should contain "demo is shorthand for the Heroku app awesomeapp-demo"
43
+
44
+ Scenario: Tag information can be listed
45
+ Given a file named "config/heroku.yml" with:
46
+ """
47
+ production:
48
+ app: awesomeapp
49
+ tag: ci/*
50
+ staging:
51
+ app: awesomeapp-staging
52
+ tag: staging/*
53
+ demo:
54
+ app: awesomeapp-demo
55
+ """
56
+
57
+ When I run `rake --trace all heroku:apps:local`
58
+
59
+ Then the output should contain "the production TAG is 'ci/*'"
60
+ And the output should contain "the staging TAG is 'staging/*'"
61
+
62
+ Scenario: heroku:create_config
63
+ When I run `rake --trace heroku:create_config`
64
+ Then a file named "config/heroku.yml" should exist
65
+ And the output should match /Copied example config to ".*.config.heroku.yml"/
66
+ And the output should match /Please edit ".*.config.heroku.yml" with your application's settings./
67
+
68
+ Scenario: heroku:create_config with an EDITOR set in the environment
69
+ When I run `rake EDITOR=echo --trace heroku:create_config`
70
+ And the output should match /Copied example config to ".*.config.heroku.yml"/
71
+ And the output should match /^echo .*.config.heroku.yml$/
@@ -1,6 +1,6 @@
1
1
  Feature: Extended config
2
2
 
3
- Scenario: Config information can be pulled from a separate git repository
3
+ Background:
4
4
  Given I run `rails new heroku_san_test -O`
5
5
  And I cd to "heroku_san_test"
6
6
  And I overwrite "Gemfile" with:
@@ -8,6 +8,7 @@ Feature: Extended config
8
8
  source :rubygems
9
9
  gem 'heroku_san', :path => '../../../.'
10
10
  """
11
+ Scenario: Config information can be pulled from a separate git repository
11
12
  Given a file named "config/heroku.yml" with:
12
13
  """
13
14
  config_repo: 'file:///<%= File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', '..', 'features', 'data', 'test-config') %>'
@@ -19,19 +20,12 @@ Feature: Extended config
19
20
  app: awesomeapp-demo
20
21
  """
21
22
 
22
- When I run `rake all heroku:config:list:local`
23
+ When I run `rake --trace all heroku:config:list:local`
23
24
 
24
25
  Then the output should contain "production TEST_REMOTE: 'hello_world'"
25
26
  And the output should contain "staging TEST_REMOTE: 'goodbye_world'"
26
27
 
27
28
  Scenario: Config information can be listed
28
- Given I run `rails new heroku_san_test -O`
29
- And I cd to "heroku_san_test"
30
- And I overwrite "Gemfile" with:
31
- """
32
- source :rubygems
33
- gem 'heroku_san', :path => '../../../.'
34
- """
35
29
  Given a file named "config/heroku.yml" with:
36
30
  """
37
31
  production:
@@ -46,19 +40,12 @@ Feature: Extended config
46
40
  app: awesomeapp-demo
47
41
  """
48
42
 
49
- When I run `rake all heroku:config:list:local`
43
+ When I run `rake --trace all heroku:config:list:local`
50
44
 
51
45
  Then the output should contain "production TEST_LOCAL: 'hello_world'"
52
46
  And the output should contain "staging TEST_LOCAL: 'goodbye_world'"
53
47
 
54
48
  Scenario: Config information can be merged between local and remote
55
- Given I run `rails new heroku_san_test -O`
56
- And I cd to "heroku_san_test"
57
- And I overwrite "Gemfile" with:
58
- """
59
- source :rubygems
60
- gem 'heroku_san', :path => '../../../.'
61
- """
62
49
  Given a file named "config/heroku.yml" with:
63
50
  """
64
51
  config_repo: 'file:///<%= File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', '..', 'features', 'data', 'test-config') %>'
@@ -73,7 +60,7 @@ Feature: Extended config
73
60
  TEST_REMOTE: 'overridden_by_remote'
74
61
  """
75
62
 
76
- When I run `rake all heroku:config:list:local`
63
+ When I run `rake --trace all heroku:config:list:local`
77
64
 
78
65
  Then the output should contain "production TEST_LOCAL: 'hello_world'"
79
66
  And the output should contain "production TEST_REMOTE: 'hello_world'"
@@ -0,0 +1,57 @@
1
+ @announce @slow_process
2
+ Feature: Works with Heroku
3
+
4
+ Background:
5
+ Given I run `git clone git://github.com/kmayer/heroku_san_demo.git`
6
+ And I cd to "heroku_san_demo"
7
+ And I run `bundle install`
8
+
9
+ Scenario: Remote commands
10
+ When I run `rake demo deploy`
11
+ Then the output should match /(http:.*-demo.heroku.com deployed to Heroku)|(Everything up-to-date)/
12
+
13
+ When I run `rake demo heroku:maintenance_on`
14
+ Then the output should contain "Maintenance mode enabled."
15
+
16
+ When I run `rake demo restart`
17
+ Then the output should contain "Restarting processes... done"
18
+
19
+ When I run `rake demo heroku:maintenance_off`
20
+ Then the output should contain "Maintenance mode disabled."
21
+
22
+ When I run `rake demo heroku:rake[db:seed]`
23
+ Then I run `curl -s http://heroku-san-demo-demo.heroku.com/droids.text`
24
+ And the output should contain "C3PO, Marvin, R2D2, Robby"
25
+
26
+ When I run `rake demo logs`
27
+ Then the output should contain "Starting process with command `rake db:seed`"
28
+
29
+ When I run `git co staging`
30
+ And I run `rake deploy`
31
+ Then the output should contain "Defaulting to 'staging' as it matches the current branch"
32
+ Then the output should match /(http:.*-staging.heroku.com deployed to Heroku)|(Everything up-to-date)/
33
+ When I run `curl -s http://heroku-san-demo-staging.heroku.com`
34
+ And the output should contain "Ruby on Rails: Welcome aboard"
35
+
36
+ When I run `rake production deploy:force`
37
+ Then the output should match /^git update-ref refs.heroku_san.deploy \w{40}$/
38
+ # Runs a before_deploy
39
+ # Runs an after_deploy
40
+ # Adds a collaborator
41
+
42
+ # Given I run `rake heroku:create_config`
43
+
44
+ # When I run `rake demo heroku:create`
45
+ # Then the output should contain "somthing about the created app"
46
+ # $ rake all heroku:create
47
+ # heroku create heroku-san-demo-demo
48
+ # Creating heroku-san-demo-demo.... done, stack is bamboo-mri-1.9.2
49
+ # http://heroku-san-demo-demo.heroku.com/ | git@heroku.com:heroku-san-demo-demo.git
50
+ # Git remote heroku added
51
+ # heroku create heroku-san-demo-production
52
+ # Creating heroku-san-demo-production... done, stack is bamboo-mri-1.9.2
53
+ # http://heroku-san-demo-production.heroku.com/ | git@heroku.com:heroku-san-demo-production.git
54
+ # heroku create heroku-san-demo-staging
55
+ # Creating heroku-san-demo-staging... done, stack is bamboo-mri-1.9.2
56
+ # http://heroku-san-demo-staging.heroku.com/ | git@heroku.com:heroku-san-demo-staging.git
57
+
@@ -6,3 +6,12 @@ require 'aruba/cucumber'
6
6
  unless File.readable? File.join(File.dirname(__FILE__), '..', 'data', 'test-config', 'config.yml')
7
7
  `git submodule init && git submodule update`
8
8
  end
9
+
10
+ Before do
11
+ @aruba_timeout_seconds = 15
12
+ end
13
+
14
+ Before('@slow_process') do
15
+ @aruba_timeout_seconds = 60
16
+ # @aruba_io_wait_seconds = 15
17
+ end
data/heroku_san.gemspec CHANGED
@@ -1,16 +1,16 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), 'lib/heroku_san/version')
2
3
 
3
4
  Gem::Specification.new do |s|
4
5
  s.name = %q{heroku_san}
5
-
6
- s.version = "1.3.0"
7
- s.date = %q{2011-11-07}
6
+ s.version = HerokuSan::VERSION
8
7
 
9
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
- s.authors = ["Elijah Miller", "Glenn Roberts", "Ryan Ahearn"]
9
+
10
+ s.authors = ["Elijah Miller", "Glenn Roberts", "Ryan Ahearn", "Ken Mayer"]
11
11
  s.description = %q{Manage multiple Heroku instances/apps for a single Rails app using Rake}
12
12
  s.email = %q{elijah.miller@gmail.com}
13
- s.homepage = %q{http://github.com/glennr/heroku_san}
13
+ s.homepage = %q{http://github.com/fastestforward/heroku_san}
14
14
  s.files = `git ls-files`.split("\n")
15
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
16
  s.require_paths = ["lib"]
@@ -24,9 +24,11 @@ Gem::Specification.new do |s|
24
24
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
25
  s.add_runtime_dependency(%q<rails>, ['>= 2'])
26
26
  s.add_runtime_dependency(%q<heroku>, ['>= 2'])
27
- s.add_development_dependency(%q<rails>, ['>= 3'])
27
+ s.add_runtime_dependency(%q<rake>)
28
28
  s.add_development_dependency(%q<aruba>)
29
29
  s.add_development_dependency(%q<cucumber>)
30
+ s.add_development_dependency(%q<rake>)
31
+ s.add_development_dependency(%q<bundler>, ['~> 1.0'])
30
32
  else
31
33
  s.add_dependency(%q<rails>, ['>= 2'])
32
34
  s.add_dependency(%q<heroku>, ['>= 2'])
data/lib/git.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'rake'
2
+ require 'rake/dsl_definition'
3
+
4
+ module Git
5
+ include Rake::DSL
6
+
7
+ def git_clone(repos, dir)
8
+ sh "git clone #{repos} #{dir}"
9
+ end
10
+
11
+ def git_active_branch
12
+ %x{git branch}.split("\n").select { |b| b =~ /^\*/ }.first.split(" ").last.strip
13
+ end
14
+
15
+ def git_push(commit, repo, options = [])
16
+ commit ||= "HEAD"
17
+ options ||= []
18
+ begin
19
+ sh "git update-ref refs/heroku_san/deploy #{commit}"
20
+ sh "git push #{repo} #{options.join(' ')} refs/heroku_san/deploy:refs/heads/master"
21
+ ensure
22
+ sh "git update-ref -d refs/heroku_san/deploy"
23
+ end
24
+ end
25
+
26
+ def git_tag(glob)
27
+ return nil if glob.nil?
28
+ %x{git tag -l '#{glob}'}.split("\n").last
29
+ end
30
+
31
+ def git_rev_parse(ref)
32
+ return nil if ref.nil?
33
+ %x{git rev-parse #{ref}}.split("\n").first
34
+ end
35
+
36
+ def git_parsed_tag(tag)
37
+ git_rev_parse(git_tag(tag))
38
+ end
39
+ end
@@ -0,0 +1,111 @@
1
+ module HerokuSan
2
+ class Project
3
+ attr_reader :config_file
4
+
5
+ include Git
6
+
7
+ def initialize(config_file)
8
+ @apps = []
9
+ @config_file = config_file
10
+ @app_settings = {}
11
+ config = parse(@config_file)
12
+ config.each do |stage, settings|
13
+ @app_settings[stage] = HerokuSan::Stage.new(stage, settings)
14
+ end
15
+ end
16
+
17
+ def create_config
18
+ template = File.expand_path(File.join(File.dirname(__FILE__), '../templates', 'heroku.example.yml'))
19
+ if File.exists?(@config_file)
20
+ false
21
+ else
22
+ FileUtils.cp(template, @config_file)
23
+ true
24
+ end
25
+ end
26
+
27
+ def all
28
+ @app_settings.keys
29
+ end
30
+
31
+ def [](stage)
32
+ @app_settings[stage]
33
+ end
34
+
35
+ def <<(*app)
36
+ app.flatten.each do |a|
37
+ @apps << a if all.include?(a)
38
+ end
39
+ self
40
+ end
41
+
42
+ def apps
43
+ if !@apps.empty?
44
+ @apps
45
+ else
46
+ case all.size
47
+ when 1
48
+ $stdout.puts "Defaulting to #{all.first.inspect} since only one app is defined"
49
+ all
50
+ else
51
+ active_branch = self.git_active_branch
52
+ all.select do |app|
53
+ app == active_branch and ($stdout.puts("Defaulting to '#{app}' as it matches the current branch") || true)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def each_app
60
+ raise NoApps if apps.empty?
61
+ apps.each do |stage|
62
+ yield(self[stage])
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def parse_yaml(config_file)
69
+ if File.exists?(config_file)
70
+ if defined?(ERB)
71
+ YAML.load(ERB.new(File.read(config_file)).result)
72
+ else
73
+ YAML.load_file(config_file)
74
+ end
75
+ else
76
+ {}
77
+ end
78
+ end
79
+
80
+ def parse(config_file)
81
+ app_settings = parse_yaml(config_file)
82
+
83
+ # support heroku_san format
84
+ if app_settings.has_key? 'apps'
85
+ app_settings = app_settings['apps']
86
+ app_settings.each_pair do |stage, app_name|
87
+ app_settings[stage] = {'app' => app_name}
88
+ end
89
+ end
90
+
91
+ # load external config
92
+ if (config_repo = app_settings.delete('config_repo'))
93
+ require 'tmpdir'
94
+ tmp_config_dir = Dir.mktmpdir
95
+ tmp_config_file = File.join tmp_config_dir, 'config.yml'
96
+ git_clone(config_repo, tmp_config_dir)
97
+ extra_config = parse_yaml(tmp_config_file)
98
+ else
99
+ extra_config = {}
100
+ end
101
+
102
+ # make sure each app has a 'config' section & merge w/extra
103
+ app_settings.keys.each do |name|
104
+ app_settings[name]['config'] ||= {}
105
+ app_settings[name]['config'].merge!(extra_config[name]) if extra_config[name]
106
+ end
107
+
108
+ app_settings
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,78 @@
1
+ module HerokuSan
2
+ class Stage
3
+ attr_reader :name
4
+
5
+ def initialize(stage, options = {})
6
+ @name = stage
7
+ @options = options
8
+ end
9
+
10
+ def app
11
+ @options['app']
12
+ end
13
+
14
+ def repo
15
+ @options['repo'] ||= "git@heroku.com:#{app}.git"
16
+ end
17
+
18
+ def stack
19
+ @options['stack'] ||= %x"heroku stack --app #{app}".split("\n").select { |b| b =~ /^\* / }.first.gsub(/^\* /, '')
20
+ end
21
+
22
+ def tag
23
+ @options['tag']
24
+ end
25
+
26
+ def config
27
+ @options['config'] ||= {}
28
+ end
29
+
30
+ def run(command, args = nil)
31
+ if stack =~ /cedar/
32
+ sh_heroku "run #{command} #{args}"
33
+ else
34
+ sh_heroku "run:#{command} #{args}"
35
+ end
36
+ end
37
+
38
+ def migrate
39
+ run 'rake', 'db:migrate'
40
+ sh_heroku "restart"
41
+ end
42
+
43
+ def maintenance(action)
44
+ raise ArgumentError, "Action #{action.inspect} must be one of (:on, :off)", caller if ![:on, :off].include?(action)
45
+ sh_heroku "maintenance:#{action}"
46
+ end
47
+
48
+ def create
49
+ sh "heroku apps:create #{app}" + (@options['stack'] ? " --stack #{@options['stack']}" : '')
50
+ end
51
+
52
+ def sharing_add(email)
53
+ sh_heroku "sharing:add #{email.chomp}"
54
+ end
55
+
56
+ def sharing_remove(email)
57
+ sh_heroku "sharing:remove #{email.chomp}"
58
+ end
59
+
60
+ def long_config
61
+ sh_heroku 'config --long'
62
+ end
63
+
64
+ def restart
65
+ sh_heroku 'restart'
66
+ end
67
+
68
+ def logs
69
+ sh_heroku 'logs'
70
+ end
71
+
72
+ private
73
+
74
+ def sh_heroku command
75
+ sh "heroku #{command} --app #{app}"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module HerokuSan
2
+ VERSION = "2.0.rc.1"
3
+ end
data/lib/heroku_san.rb CHANGED
@@ -1 +1,9 @@
1
- require 'heroku_san/railtie.rb' if defined?(Rails) && Rails::VERSION::MAJOR == 3
1
+ require 'railtie' if defined?(Rails) && Rails::VERSION::MAJOR == 3
2
+ require 'git'
3
+ require 'heroku_san/stage'
4
+ require 'heroku_san/project'
5
+
6
+ module HerokuSan
7
+ class NoApps < StandardError; end
8
+ class Deprecated < StandardError; end
9
+ end
@@ -4,7 +4,7 @@ require 'rails'
4
4
  module HerokuSan
5
5
  class Railtie < Rails::Railtie
6
6
  rake_tasks do
7
- load 'heroku_san/tasks.rb'
7
+ load 'tasks.rb'
8
8
  end
9
9
  end
10
10
  end
@@ -1 +1 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'heroku_san', 'tasks'))
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'tasks'))