| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,81 +1,80 @@ | ||
| Feature: Extended config | ||
|
|
||
| Background: | ||
| Given a directory named "test-config" | ||
| And I cd to "test-config" | ||
| And I run `git init .` | ||
| And a file named "config.yml" with: | ||
| """ | ||
| production: | ||
| TEST_REMOTE: 'hello_production' | ||
| staging: | ||
| TEST_REMOTE: 'goodbye_staging' | ||
| """ | ||
| And I run `git add .` | ||
| And I run `git commit -m 'Initial commit'` | ||
| And I cd to ".." | ||
| Given I run `rails new heroku_san_test -O` | ||
| And I cd to "heroku_san_test" | ||
| And I overwrite "Gemfile" with: | ||
| """ | ||
| source :rubygems | ||
| gem 'rails' | ||
| gem 'heroku_san', :path => '../../../.' | ||
| """ | ||
|
|
||
| Scenario: Config information can be pulled from a separate git repository | ||
| Given a file named "config/heroku.yml" with: | ||
| """ | ||
| config_repo: 'file:///<%= File.join(File.expand_path(File.dirname(__FILE__)), '..', 'test-config') %>' | ||
| production: | ||
| app: awesomeapp | ||
| staging: | ||
| app: awesomeapp-staging | ||
| demo: | ||
| app: awesomeapp-demo | ||
| """ | ||
| When I run `rake --trace all heroku:config:list:local` | ||
|
|
||
| Then the output should contain "TEST_REMOTE: hello_production" | ||
| And the output should contain "TEST_REMOTE: goodbye_staging" | ||
|
|
||
| Scenario: Config information can be listed | ||
| Given a file named "config/heroku.yml" with: | ||
| """ | ||
| production: | ||
| app: awesomeapp | ||
| config: | ||
| TEST_LOCAL: 'hello_production' | ||
| staging: | ||
| app: awesomeapp-staging | ||
| config: | ||
| TEST_LOCAL: 'goodbye_staging' | ||
| demo: | ||
| app: awesomeapp-demo | ||
| """ | ||
| When I run `rake --trace all heroku:config:list:local` | ||
|
|
||
| Then the output should contain "TEST_LOCAL: hello_production" | ||
| And the output should contain "TEST_LOCAL: goodbye_staging" | ||
|
|
||
| Scenario: Config information can be merged between local and remote | ||
| Given a file named "config/heroku.yml" with: | ||
| """ | ||
| config_repo: 'file:///<%= File.join(File.expand_path(File.dirname(__FILE__)), '..', 'test-config') %>' | ||
| production: | ||
| app: awesomeapp | ||
| config: | ||
| TEST_LOCAL: 'hello_production' | ||
| staging: | ||
| app: awesomeapp-staging | ||
| config: | ||
| TEST_LOCAL: 'goodbye_staging' | ||
| TEST_REMOTE: 'overridden_by_remote' | ||
| """ | ||
| When I run `rake --trace all heroku:config:list:local` | ||
|
|
||
| Then the output should contain "TEST_LOCAL: hello_production" | ||
| And the output should contain "TEST_REMOTE: hello_production" | ||
| And the output should contain "TEST_LOCAL: goodbye_staging" | ||
| And the output should contain "TEST_REMOTE: goodbye_staging" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| @announce-cmd @slow_process | ||
| Feature: heroku_san can control a project on Heroku | ||
| WARNING: This WILL create apps on Heroku! | ||
| You must login with the heroku cli before starting | ||
| this test; otherwise it will probably hang the first | ||
| time it tries to do anything with Heroku itself. | ||
|
|
||
| Scenario: Installing on a project | ||
| Given I have a new Rails project | ||
| When I am in the project directory | ||
| And I add heroku_san to the Gemfile | ||
| And I run bundle install | ||
| Then rake reports that the heroku: tasks are available | ||
|
|
||
| Scenario: Manipulates the project on Heroku | ||
| Given I have a new Rails project | ||
| When I am in the project directory | ||
| And I add heroku_san to the Gemfile | ||
| And I run bundle install | ||
| And I create a new config/heroku.yml file | ||
| And I create my project on Heroku | ||
| And I list the remote configuration | ||
| And I curl the app home page | ||
| And I configure my project | ||
| And I turn maintenance on | ||
| And I deploy my project | ||
| And I turn maintenance off | ||
| And I restart my project | ||
| And I list all apps on Heroku | ||
| Then heroku_san is green |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| World(Aruba::Api) | ||
|
|
||
| Given /^I have a new Rails project$/ do | ||
| # template = File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', 'features', 'data', 'template.rb') | ||
| cmd = "rails new heroku_san_test --quiet --force --database=postgresql --skip-bundle --skip-javascript --skip-test-unit --skip-sprockets" #" --template #{template}" | ||
| run_simple unescape(cmd) | ||
| end | ||
|
|
||
| When /^I am in the project directory$/ do | ||
| cd '/heroku_san_test' | ||
| end | ||
|
|
||
| When /^I add heroku_san to the Gemfile$/ do | ||
| append_to_file 'Gemfile', <<EOT | ||
| group :development, :test do | ||
| gem 'heroku_san', :path => '../../../.' | ||
| end | ||
| EOT | ||
| end | ||
|
|
||
| When /^I run bundle install$/ do | ||
| use_clean_gemset 'heroku_san_test' | ||
| run_simple 'bundle install --quiet' | ||
| write_file '.rvmrc', "rvm use default@heroku_san_test\n" | ||
| end | ||
|
|
||
| Then /^rake reports that the heroku: tasks are available$/ do | ||
| run_simple 'rake -T heroku:' | ||
| assert_partial_output 'rake heroku:apps', all_output | ||
| end | ||
|
|
||
| When /^I create a new config\/heroku\.yml file$/ do | ||
| run_simple 'rake heroku:create_config' | ||
| assert_matching_output %q{Copied example config to ".*.config.heroku.yml"}, all_output | ||
| assert_matching_output %q{Please edit ".*.config.heroku.yml" with your application's settings.}, all_output | ||
| overwrite_file 'config/heroku.yml', <<EOT | ||
| --- | ||
| test_app: | ||
| EOT | ||
| end | ||
|
|
||
| When /^I create my project on Heroku$/ do | ||
| cmd = 'rake test_app heroku:create' | ||
| run_simple unescape(cmd) | ||
| assert_matching_output %q{test_app: Created ([\w-]+)}, all_output | ||
| output = stdout_from cmd | ||
| @app = output.match(/test_app: Created ([\w-]+)/)[1] | ||
| overwrite_file 'config/heroku.yml', <<EOT | ||
| --- | ||
| test_app: | ||
| app: #{@app} | ||
| EOT | ||
| end | ||
|
|
||
| When /^I list the remote configuration$/ do | ||
| cmd = 'rake test_app heroku:config:list' | ||
| run_simple unescape(cmd) | ||
| assert_partial_output "APP_NAME: #{@app}", all_output | ||
| assert_partial_output "URL: #{@app}.heroku.com", all_output | ||
| output = stdout_from cmd | ||
| @url = output.match(/\bURL:\s+(.*.heroku.com)\b/)[1] | ||
| @curl = unescape("curl --silent http://#{@url}") | ||
| end | ||
|
|
||
| When /^I curl the app home page$/ do | ||
| run_simple @curl | ||
| output = stdout_from @curl | ||
| assert_partial_output '<h1><strong>Heroku | Welcome to your new app!</strong></h1>', output | ||
| end | ||
|
|
||
| When /^I configure my project$/ do | ||
| overwrite_file 'config/heroku.yml', <<EOT | ||
| --- | ||
| test_app: | ||
| app: #{@app} | ||
| config: | ||
| DROIDS: marvin | ||
| EOT | ||
| cmd = 'rake test_app heroku:config' | ||
| run_simple unescape(cmd) | ||
| assert_partial_output 'DROIDS: marvin', all_output | ||
| end | ||
|
|
||
| When /^I turn maintenance on$/ do | ||
| run_simple 'rake test_app heroku:maintenance_on' | ||
| assert_partial_output 'test_app: Maintenance mode enabled.', all_output | ||
| run_simple @curl | ||
| output = stdout_from @curl | ||
| assert_partial_output '<title>Offline for Maintenance</title>', all_output | ||
| end | ||
|
|
||
| When /^I turn maintenance off$/ do | ||
| run_simple 'rake test_app heroku:maintenance_off' | ||
| assert_partial_output 'test_app: Maintenance mode disabled.', all_output | ||
| run_simple @curl + "/droids" | ||
| assert_partial_output %Q{<code>marvin</code>}, all_output | ||
| end | ||
|
|
||
| When /^I restart my project$/ do | ||
| run_simple 'rake test_app heroku:restart' | ||
| assert_partial_output 'test_app: Restarted.', all_output | ||
| end | ||
|
|
||
| When /^I deploy my project$/ do | ||
| run_simple 'git init .' | ||
| run_simple 'rails generate scaffold droids' | ||
| append_to_file 'app/views/droids/index.html.erb', %Q{\n<div><code><%= ENV['DROIDS'] -%></code></div>\n} | ||
| run_simple 'git add .' | ||
| run_simple 'git commit -m "Initial commit"' | ||
| run_simple 'rake test_app deploy' | ||
| assert_partial_output "http://#{@app}.heroku.com deployed to Heroku", all_output | ||
| end | ||
|
|
||
| When /^I list all apps on Heroku$/ do | ||
| run_simple 'rake heroku:apps' | ||
| assert_partial_output "test_app is shorthand for the Heroku app #{@app} located at:", all_output | ||
| assert_partial_output "git@heroku.com:#{@app}.git", all_output | ||
| assert_matching_output '@ \w{40} master', all_output | ||
| end | ||
|
|
||
| Then /^heroku_san is green$/ do | ||
| run_simple "heroku apps:destroy #{@app} --confirm #{@app}" | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| require 'rake' | ||
| require 'rake/dsl_definition' | ||
|
|
||
| module Git | ||
| include Rake::DSL | ||
|
|
||
| def git_clone(repos, dir) | ||
| sh "git clone #{repos} #{dir}" | ||
| end | ||
|
|
||
| def git_active_branch | ||
| %x{git branch}.split("\n").select { |b| b =~ /^\*/ }.first.split(" ").last.strip | ||
| end | ||
|
|
||
| def git_push(commit, repo, options = []) | ||
| commit ||= "HEAD" | ||
| options ||= [] | ||
| begin | ||
| sh "git update-ref refs/heroku_san/deploy #{commit}" | ||
| sh "git push #{repo} #{options.join(' ')} refs/heroku_san/deploy:refs/heads/master" | ||
| ensure | ||
| sh "git update-ref -d refs/heroku_san/deploy" | ||
| end | ||
| end | ||
|
|
||
| def git_parsed_tag(tag) | ||
| git_rev_parse(git_tag(tag)) | ||
| end | ||
|
|
||
| def git_rev_parse(ref) | ||
| return nil if ref.nil? | ||
| %x{git rev-parse #{ref}}.split("\n").first | ||
| end | ||
|
|
||
| def git_tag(glob) | ||
| return nil if glob.nil? | ||
| %x{git tag -l '#{glob}'}.split("\n").last | ||
| end | ||
|
|
||
| def git_revision(repo) | ||
| %x{git ls-remote --heads #{repo} master}.split.first | ||
| end | ||
|
|
||
| def git_named_rev(ref) | ||
| %x{git name-rev #{ref}}.chomp | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,10 @@ | ||
| require 'railtie' if defined?(Rails) && Rails::VERSION::MAJOR == 3 | ||
| require 'git' | ||
| require 'heroku_san/stage' | ||
| require 'heroku_san/project' | ||
|
|
||
| module HerokuSan | ||
| class NoApps < StandardError; end | ||
| class MissingApp < StandardError; end | ||
| class Deprecated < StandardError; end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| module HerokuSan | ||
| class Project | ||
| attr_reader :config_file | ||
|
|
||
| include Git | ||
|
|
||
| def initialize(config_file) | ||
| @apps = [] | ||
| @config_file = config_file | ||
| @app_settings = {} | ||
| config = parse(@config_file) | ||
| config.each do |stage, settings| | ||
| @app_settings[stage] = HerokuSan::Stage.new(stage, settings) | ||
| end | ||
| end | ||
|
|
||
| def create_config | ||
| template = File.expand_path(File.join(File.dirname(__FILE__), '../templates', 'heroku.example.yml')) | ||
| if File.exists?(@config_file) | ||
| false | ||
| else | ||
| FileUtils.cp(template, @config_file) | ||
| true | ||
| end | ||
| end | ||
|
|
||
| def all | ||
| @app_settings.keys | ||
| end | ||
|
|
||
| def [](stage) | ||
| @app_settings[stage] | ||
| end | ||
|
|
||
| def <<(*app) | ||
| app.flatten.each do |a| | ||
| @apps << a if all.include?(a) | ||
| end | ||
| self | ||
| end | ||
|
|
||
| def apps | ||
| if !@apps.empty? | ||
| @apps | ||
| else | ||
| case all.size | ||
| when 1 | ||
| $stdout.puts "Defaulting to #{all.first.inspect} since only one app is defined" | ||
| all | ||
| else | ||
| active_branch = self.git_active_branch | ||
| all.select do |app| | ||
| app == active_branch and ($stdout.puts("Defaulting to '#{app}' as it matches the current branch") || true) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def each_app | ||
| raise NoApps if apps.empty? | ||
| apps.each do |stage| | ||
| yield(self[stage]) | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def parse_yaml(config_file) | ||
| if File.exists?(config_file) | ||
| if defined?(ERB) | ||
| YAML.load(ERB.new(File.read(config_file)).result) | ||
| else | ||
| YAML.load_file(config_file) | ||
| end | ||
| else | ||
| {} | ||
| end | ||
| end | ||
|
|
||
| def parse(config_file) | ||
| app_settings = parse_yaml(config_file) | ||
|
|
||
| # support heroku_san format | ||
| if app_settings.has_key? 'apps' | ||
| app_settings = app_settings['apps'] | ||
| app_settings.each_pair do |stage, app_name| | ||
| app_settings[stage] = {'app' => app_name} | ||
| end | ||
| end | ||
|
|
||
| # load external config | ||
| if (config_repo = app_settings.delete('config_repo')) | ||
| require 'tmpdir' | ||
| tmp_config_dir = Dir.mktmpdir | ||
| tmp_config_file = File.join tmp_config_dir, 'config.yml' | ||
| git_clone(config_repo, tmp_config_dir) | ||
| extra_config = parse_yaml(tmp_config_file) | ||
| else | ||
| extra_config = {} | ||
| end | ||
|
|
||
| # make sure each app has a 'config' section & merge w/extra | ||
| app_settings.keys.each do |name| | ||
| app_settings[name] ||= {} | ||
| app_settings[name]['config'] ||= {} | ||
| app_settings[name]['config'].merge!(extra_config[name]) if extra_config[name] | ||
| end | ||
|
|
||
| app_settings | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| require 'heroku' | ||
| require 'json' | ||
|
|
||
| module HerokuSan | ||
| class Stage | ||
| attr_reader :name | ||
| include Git | ||
|
|
||
| def initialize(stage, options = {}) | ||
| @name = stage | ||
| @options = options | ||
| end | ||
|
|
||
| def heroku | ||
| Heroku::Auth.client | ||
| end | ||
|
|
||
| def app | ||
| @options['app'] or raise MissingApp, "#{name}: is missing the app: configuration value. I don't know what to access on Heroku." | ||
| end | ||
|
|
||
| def repo | ||
| @options['repo'] ||= "git@heroku.com:#{app}.git" | ||
| end | ||
|
|
||
| def stack | ||
| @options['stack'] ||= heroku.list_stacks(app).detect{|stack| stack['current']}['name'] | ||
| end | ||
|
|
||
| def tag | ||
| @options['tag'] | ||
| end | ||
|
|
||
| def config | ||
| @options['config'] ||= {} | ||
| end | ||
|
|
||
| def run(command, args = nil) | ||
| if stack =~ /cedar/ | ||
| sh_heroku "run #{command} #{args}" | ||
| else | ||
| sh_heroku "run:#{command} #{args}" | ||
| end | ||
| end | ||
|
|
||
| def deploy(sha = nil, force = false) | ||
| sha ||= git_parsed_tag(tag) | ||
| git_push(sha, repo, force ? %w[--force] : []) | ||
| end | ||
|
|
||
| def migrate | ||
| rake('db:migrate') | ||
| restart | ||
| end | ||
|
|
||
| def rake(*args) | ||
| run 'rake', args.join(' ') | ||
| # heroku.rake app, args.join(' ') | ||
| end | ||
|
|
||
| def maintenance(action = nil) | ||
| if block_given? | ||
| heroku.maintenance(app, :on) | ||
| begin | ||
| yield | ||
| ensure | ||
| heroku.maintenance(app, :off) | ||
| end | ||
| else | ||
| raise ArgumentError, "Action #{action.inspect} must be one of (:on, :off)", caller if ![:on, :off].include?(action) | ||
| heroku.maintenance(app, action) | ||
| end | ||
| end | ||
|
|
||
| def create # DEPREC? | ||
| if @options['stack'] | ||
| heroku.create(@options['app'], {:stack => @options['stack']}) | ||
| else | ||
| heroku.create(@options['app']) | ||
| end | ||
| end | ||
|
|
||
| def sharing_add(email) # DEPREC? | ||
| sh_heroku "sharing:add #{email.chomp}" | ||
| end | ||
|
|
||
| def sharing_remove(email) # DEPREC? | ||
| sh_heroku "sharing:remove #{email.chomp}" | ||
| end | ||
|
|
||
| def long_config | ||
| heroku.config_vars(app) | ||
| end | ||
|
|
||
| def push_config(options = nil) | ||
| JSON.parse(heroku.add_config_vars(app, options || config)) | ||
| end | ||
|
|
||
| def restart | ||
| heroku.ps_restart(app) | ||
| end | ||
|
|
||
| def logs(tail = false) | ||
| sh_heroku 'logs' + (tail ? ' --tail' : '') | ||
| end | ||
|
|
||
| def revision | ||
| git_named_rev(git_revision(repo)) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def sh_heroku(command) | ||
| sh "heroku #{command} --app #{app}" | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module HerokuSan | ||
| VERSION = "2.1.1" | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| module HerokuSan | ||
| class Railtie < Rails::Railtie | ||
| rake_tasks do | ||
| load 'tasks.rb' | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,312 @@ | ||
| require 'git' | ||
| include Git | ||
|
|
||
| @heroku_san = HerokuSan::Project.new(Rails.root.join('config', 'heroku.yml')) | ||
|
|
||
| @heroku_san.all.each do |stage| | ||
| desc "Select #{stage} Heroku app for later commands" | ||
| task "heroku:stage:#{stage}" do | ||
| @heroku_san << stage | ||
| end | ||
| task stage => "heroku:stage:#{stage}" | ||
| end | ||
|
|
||
| namespace :heroku do | ||
| desc 'Select all Heroku apps for later command' | ||
| task 'stage:all' do | ||
| @heroku_san << @heroku_san.all | ||
| end | ||
|
|
||
| desc "Creates the Heroku app" | ||
| task :create do | ||
| each_heroku_app do |stage| | ||
| puts "#{stage.name}: Created #{stage.create}" | ||
| end | ||
| end | ||
|
|
||
| #desc "Generate the Heroku gems manifest from gem dependencies" | ||
| task :gems => 'gems:base' do | ||
| raise HerokuSan::Deprecated | ||
| end | ||
|
|
||
| desc 'Add git remotes for all apps in this project' | ||
| task :remotes do | ||
| each_heroku_app do |stage| | ||
| sh "git remote add #{stage.name} #{stage.repo}" | ||
| end | ||
| end | ||
|
|
||
| desc 'Adds a collaborator (asks for email)' | ||
| task :share do | ||
| print "Email address of collaborator to add: " | ||
| $stdout.flush | ||
| email = $stdin.gets | ||
| each_heroku_app do |stage| | ||
| stage.sharing_add email | ||
| end | ||
| end | ||
|
|
||
| desc 'Removes a collaborator (asks for email)' | ||
| task :unshare do | ||
| print "Email address of collaborator to remove: " | ||
| $stdout.flush | ||
| email = $stdin.gets | ||
| each_heroku_app do |stage| | ||
| stage.sharing_remove email | ||
| end | ||
| end | ||
|
|
||
| desc 'Lists configured apps' | ||
| task :apps => :all do | ||
| each_heroku_app do |stage| | ||
| rev = stage.revision | ||
| puts "#{stage.name} is shorthand for the Heroku app #{stage.app} located at:" | ||
| puts " #{stage.repo}" | ||
| puts " @ #{rev.blank? ? 'not deployed' : rev}" | ||
| puts | ||
| end | ||
| end | ||
|
|
||
| namespace :apps do | ||
| desc 'Lists configured apps without hitting heroku' | ||
| task :local => :all do | ||
| each_heroku_app do |stage| | ||
| puts "#{stage.name} is shorthand for the Heroku app #{stage.app} located at:" | ||
| puts " #{stage.repo}" | ||
| puts " the #{stage.name} TAG is '#{stage.tag}'" if stage.tag | ||
| puts | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc 'Add config:vars to each application.' | ||
| task :config do | ||
| each_heroku_app do |stage| | ||
| puts y(stage.push_config) | ||
| end | ||
| end | ||
|
|
||
| desc 'Creates an example configuration file' | ||
| task :create_config do | ||
| filename = %Q{#{@heroku_san.config_file.to_s}} | ||
| if @heroku_san.create_config | ||
| puts "Copied example config to #{filename.inspect}" | ||
| if ENV['EDITOR'] && ENV['EDITOR'] != '' | ||
| sh "#{ENV['EDITOR']} #{filename}" | ||
| else | ||
| puts "Please edit #{filename.inspect} with your application's settings." | ||
| end | ||
| else | ||
| puts "#{filename.inspect} already exists" | ||
| end | ||
| end | ||
|
|
||
| namespace :config do | ||
| desc 'Add proper RACK_ENV to each application' | ||
| task :rack_env => :all do | ||
| each_heroku_app do |stage| | ||
| command = "heroku config --app #{stage.app}" | ||
| puts command | ||
| config = Hash[`#{command}`.scan(/^(.+?)\s*=>\s*(.+)$/)] | ||
| if config['RACK_ENV'] != stage.name | ||
| puts stage.push_config RACK_ENV: stage.name | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc "Lists config variables as set on Heroku" | ||
| task :list do | ||
| each_heroku_app do |stage| | ||
| puts "#{stage.name}:" | ||
| puts y(stage.long_config) | ||
| end | ||
| end | ||
|
|
||
| namespace :list do | ||
| desc "Lists local config variables without setting them" | ||
| task :local do | ||
| each_heroku_app do |stage| | ||
| puts "#{stage.name}:" | ||
| puts y(stage.config) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc 'Runs a rake task remotely' | ||
| task :rake, [:task] do |t, args| | ||
| each_heroku_app do |stage| | ||
| puts stage.rake args.task | ||
| end | ||
| end | ||
|
|
||
| desc "Pushes the given commit (default: HEAD)" | ||
| task :push, :commit do |t, args| | ||
| each_heroku_app do |stage| | ||
| stage.deploy(args[:commit]) | ||
| end | ||
| end | ||
|
|
||
| namespace :push do | ||
| desc "Force-pushes the given commit (default: HEAD)" | ||
| task :force, :commit do |t, args| | ||
| each_heroku_app do |stage| | ||
| stage.deploy(args[:commit], :force) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc "Enable maintenance mode" | ||
| task :maintenance do | ||
| each_heroku_app do |stage| | ||
| stage.maintenance :on | ||
| puts "#{stage.name}: Maintenance mode enabled." | ||
| end | ||
| end | ||
|
|
||
| desc "Enable maintenance mode" | ||
| task :maintenance_on do | ||
| each_heroku_app do |stage| | ||
| stage.maintenance :on | ||
| puts "#{stage.name}: Maintenance mode enabled." | ||
| end | ||
| end | ||
|
|
||
| desc "Disable maintenance mode" | ||
| task :maintenance_off do | ||
| each_heroku_app do |stage| | ||
| stage.maintenance :off | ||
| puts "#{stage.name}: Maintenance mode disabled." | ||
| end | ||
| end | ||
|
|
||
| desc "Pushes the given commit, migrates and restarts (default: HEAD)" | ||
| task :deploy, [:commit] => [:before_deploy] do |t, args| | ||
| each_heroku_app do |stage| | ||
| stage.deploy(args[:commit]) | ||
| stage.migrate | ||
| end | ||
| Rake::Task[:after_deploy].execute | ||
| end | ||
|
|
||
| namespace :deploy do | ||
| desc "Force-pushes the given commit, migrates and restarts (default: HEAD)" | ||
| task :force, [:commit] => [:before_deploy] do |t, args| | ||
| each_heroku_app do |stage| | ||
| stage.deploy(args[:commit], :force) | ||
| stage.migrate | ||
| end | ||
| Rake::Task[:after_deploy].execute | ||
| end | ||
|
|
||
| desc "Callback before deploys" | ||
| task :before do | ||
| end | ||
|
|
||
| desc "Callback after deploys" | ||
| task :after do | ||
| end | ||
|
|
||
| end | ||
|
|
||
| task :force_deploy do | ||
| raise HerokuSan::Deprecated | ||
| end | ||
|
|
||
| #desc "Captures a bundle on Heroku" | ||
| task :capture do | ||
| raise HerokuSan::Deprecated | ||
| end | ||
|
|
||
| desc "Opens a remote console" | ||
| task :console do | ||
| each_heroku_app do |stage| | ||
| stage.run 'console' | ||
| end | ||
| end | ||
|
|
||
| desc "Restarts remote servers" | ||
| task :restart do | ||
| each_heroku_app do |stage| | ||
| stage.restart | ||
| puts "#{stage.name}: Restarted." | ||
| end | ||
| end | ||
|
|
||
| namespace :logs do | ||
| task :default do | ||
| each_heroku_app do |stage| | ||
| stage.logs | ||
| end | ||
| end | ||
|
|
||
| desc "Tail the Heroku logs (requires logging:expanded)" | ||
| task :tail do | ||
| each_heroku_app do |stage| | ||
| stage.logs(:tail) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc "Shows the Heroku logs" | ||
| task :logs => 'logs:default' | ||
|
|
||
| namespace :db do | ||
| desc "Migrates and restarts remote servers" | ||
| task :migrate do | ||
| each_heroku_app do |stage| | ||
| stage.migrate | ||
| end | ||
| end | ||
|
|
||
| desc "Pull database from stage to local dev database" | ||
| task :pull do | ||
| each_heroku_app do |stage| | ||
| sh "heroku pgdumps:capture --app #{stage.app}" | ||
| dump = `heroku pgdumps --app #{stage.app}`.split("\n").last.split(" ").first | ||
| sh "mkdir -p #{Rails.root}/db/dumps" | ||
| file = "#{Rails.root}/db/dumps/#{dump}.sql.gz" | ||
| url = `heroku pgdumps:url --app #{stage.app} #{dump}`.chomp | ||
| sh "wget", url, "-O", file | ||
| sh "rake db:drop db:create" | ||
| sh "gunzip -c #{file} | #{Rails.root}/script/dbconsole" | ||
| sh "rake jobs:clear" | ||
| end | ||
| end | ||
| end | ||
|
|
||
| desc "Run a bash shell on Heroku" | ||
| task :shell do | ||
| each_heroku_app do |stage| | ||
| stage.run 'bash' | ||
| end | ||
| end | ||
| end | ||
|
|
||
| task :all => 'heroku:stage:all' | ||
| task :deploy => 'heroku:deploy' | ||
| task 'deploy:force' => 'heroku:deploy:force' | ||
| task :before_deploy => 'heroku:deploy:before' | ||
| task :after_deploy => 'heroku:deploy:after' | ||
| task :console => 'heroku:console' | ||
| task :restart => 'heroku:restart' | ||
| task :migrate => 'heroku:db:migrate' | ||
| task :logs => 'heroku:logs:default' | ||
| task 'logs:tail' => 'heroku:logs:tail' | ||
| task 'heroku:rack_env' => 'heroku:config:rack_env' | ||
| task :shell => 'heroku:shell' | ||
|
|
||
| def each_heroku_app(&block) | ||
| @heroku_san.each_app(&block) | ||
| puts | ||
| rescue HerokuSan::NoApps => e | ||
| puts "You must first specify at least one Heroku app: | ||
| rake <app> [<app>] <command> | ||
| rake production restart | ||
| rake demo staging deploy" | ||
|
|
||
| puts "\nYou can use also command all Heroku apps for this project: | ||
| rake all heroku:share" | ||
|
|
||
| exit(1) | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| require File.expand_path(File.join(File.dirname(__FILE__), '..', 'tasks')) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # | ||
| # Format: | ||
| # | ||
| # <heroku_san shorthand name>: | ||
| # app: <Heroku app name> | ||
| # tag: <git tag pattern> | ||
| # config: | ||
| # - <Heroku config:var name>: <Heroku config:var value> | ||
| # | ||
| production: | ||
| app: awesomeapp | ||
| tag: production/* | ||
| config: | ||
| BUNDLE_WITHOUT: "development:test" | ||
| GOOGLE_ANALYTICS: "UA-12345678-1" | ||
|
|
||
| staging: | ||
| app: awesomeapp-staging | ||
| stack: bamboo-ree-1.8.7 | ||
| config: &default | ||
| BUNDLE_WITHOUT: "development:test" | ||
|
|
||
| demo: | ||
| app: awesomeapp-demo | ||
| stack: cedar | ||
| config: *default |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| config_repo: 'file:///<%= File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', '..', 'features', 'data', 'test-config') %>' | ||
| production: | ||
| app: awesomeapp | ||
| staging: | ||
| app: awesomeapp-staging | ||
| demo: | ||
| app: awesomeapp-demo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # | ||
| # Format: | ||
| # | ||
| # apps: | ||
| # shorthand: <Heroku app name> | ||
| # | ||
| apps: | ||
| production: awesomeapp | ||
| staging: awesomeapp-staging | ||
| demo: awesomeapp-demo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| production: | ||
| app: awesomeapp | ||
| tag: production/* | ||
| config: | ||
| BUNDLE_WITHOUT: "development:test" | ||
| GOOGLE_ANALYTICS: "UA-12345678-1" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| require 'spec_helper' | ||
| require 'git' | ||
|
|
||
| class GitTest; include Git; end | ||
|
|
||
| describe GitTest do | ||
| describe "#git_push" do | ||
| it "pushes to heroku" do | ||
| subject.should_receive(:sh).with("git update-ref refs/heroku_san/deploy HEAD") | ||
| subject.should_receive(:sh).with("git push git@heroku.com:awesomeapp.git refs/heroku_san/deploy:refs/heads/master") | ||
| subject.should_receive(:sh).with("git update-ref -d refs/heroku_san/deploy") | ||
| subject.git_push(nil, 'git@heroku.com:awesomeapp.git') | ||
| end | ||
|
|
||
| it "pushes a specific commit to heroku" do | ||
| subject.should_receive(:sh).with("git update-ref refs/heroku_san/deploy kommit") | ||
| subject.should_receive(:sh).with("git push git@heroku.com:awesomeapp.git refs/heroku_san/deploy:refs/heads/master") | ||
| subject.should_receive(:sh).with("git update-ref -d refs/heroku_san/deploy") | ||
| subject.git_push('kommit', 'git@heroku.com:awesomeapp.git') | ||
| end | ||
|
|
||
| it "includes options, too" do | ||
| subject.should_receive(:sh).with("git update-ref refs/heroku_san/deploy HEAD") | ||
| subject.should_receive(:sh).with("git push git@heroku.com:awesomeapp.git --force -v refs/heroku_san/deploy:refs/heads/master") | ||
| subject.should_receive(:sh).with("git update-ref -d refs/heroku_san/deploy") | ||
| subject.git_push(nil, 'git@heroku.com:awesomeapp.git', %w[--force -v]) | ||
| end | ||
| end | ||
|
|
||
| describe "#git_tag" do | ||
| it "returns the latest tag that matches the pattern" do | ||
| subject.should_receive("`").with("git tag -l 'pattern*'") { "x\n\y\n\z\n" } | ||
| subject.git_tag('pattern*').should == "z" | ||
| end | ||
| it "returns nil if no tags match the pattern" do | ||
| subject.should_receive("`").with("git tag -l 'pattern*'") { "\n" } | ||
| subject.git_tag('pattern*').should == nil | ||
| end | ||
| it "returns nil for a nil tag" do | ||
| subject.should_not_receive("`").with("git tag -l ''") { "\n" } | ||
| subject.git_tag(nil).should == nil | ||
| end | ||
| end | ||
|
|
||
| describe "#git_rev_parse" do | ||
| it "returns the rev based on the tag" do | ||
| subject.should_receive("`").with("git rev-parse prod/1234567890") { "sha\n" } | ||
| subject.git_rev_parse('prod/1234567890').should == "sha" | ||
| end | ||
| it "returns nil for a blank tag" do | ||
| subject.should_not_receive("`").with("git rev-parse ") { "\n" } | ||
| subject.git_rev_parse(nil).should == nil | ||
| end | ||
| end | ||
|
|
||
| describe "#git_revision" do | ||
| it "returns the current revision of the repository (on Heroku)" do | ||
| subject.should_receive("`").with("git ls-remote --heads staging master") { "sha\n" } | ||
| subject.git_revision('staging').should == 'sha' | ||
| end | ||
|
|
||
| it "returns nil if there is no revision (i.e. not deployed yet)" do | ||
| subject.should_receive("`").with("git ls-remote --heads staging master") { "\n" } | ||
| subject.git_revision('staging').should == nil | ||
| end | ||
| end | ||
|
|
||
| describe "#git_named_rev" do | ||
| it "returns symbolic names for given rev" do | ||
| subject.should_receive("`").with("git name-rev sha") {"sha production/123456\n"} | ||
| subject.git_named_rev('sha').should == 'sha production/123456' | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| require 'spec_helper' | ||
| require 'tmpdir' | ||
|
|
||
| describe HerokuSan::Project do | ||
| specify ".new with a missing config file" do | ||
| heroku_san = HerokuSan::Project.new("/u/should/never/get/here") | ||
| heroku_san.all.should == [] | ||
| end | ||
|
|
||
| context "using the example config file" do | ||
| let(:heroku_config_file) { File.join(SPEC_ROOT, "fixtures", "example.yml") } | ||
| let(:template_config_file) { | ||
| path = File.join(SPEC_ROOT, "..", "lib/templates", "heroku.example.yml") | ||
| (File.respond_to? :realpath) ? File.realpath(path) : path | ||
| } | ||
| let(:heroku_san) { HerokuSan::Project.new(heroku_config_file) } | ||
| subject { heroku_san } | ||
|
|
||
| its(:all) { should =~ %w[production staging demo] } | ||
|
|
||
| context "using the heroku_san format" do | ||
| let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "old_format.yml")) } | ||
|
|
||
| it "returns a list of apps" do | ||
| heroku_san.all.should =~ %w[production staging demo] | ||
| end | ||
| end | ||
|
|
||
| describe "Adding an app to the deploy list" do | ||
| it "appends known shorthands to apps" do | ||
| heroku_san.apps.should == [] | ||
| heroku_san << 'production' | ||
| heroku_san.apps.should == %w[production] | ||
| heroku_san << 'staging' | ||
| heroku_san.apps.should == %w[production staging] | ||
| heroku_san << 'unknown' | ||
| heroku_san.apps.should == %w[production staging] | ||
| end | ||
|
|
||
| it "appends .all (or any array)" do | ||
| heroku_san << heroku_san.all | ||
| heroku_san.apps.should == heroku_san.all | ||
| end | ||
| end | ||
|
|
||
| describe "#apps extra default behaviors" do | ||
| specify "on a git branch that matches an app name" do | ||
| heroku_san.should_receive(:git_active_branch) { "staging" } | ||
| $stdout.should_receive(:puts).with("Defaulting to 'staging' as it matches the current branch") | ||
| heroku_san.apps.should == %w[staging] | ||
| end | ||
|
|
||
| specify "on a git branch that doesn't matches an app name" do | ||
| heroku_san.should_receive(:git_active_branch) { "master" } | ||
| heroku_san.apps.should == %w[] | ||
| end | ||
|
|
||
| context "but only a single configured app" do | ||
| let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "single_app.yml")) } | ||
| it "returns the app" do | ||
| $stdout.should_receive(:puts).with('Defaulting to "production" since only one app is defined') | ||
| heroku_san.apps.should == %w[production] | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "#each_app" do | ||
| it "raises an error is no apps were specified" do | ||
| expect { heroku_san.each_app do true; end }.to raise_error HerokuSan::NoApps | ||
| end | ||
|
|
||
| it "yields to a block with args" do | ||
| heroku_san << 'production' | ||
| block = double('block') | ||
| block.should_receive(:action).with(heroku_san['production']) | ||
| heroku_san.each_app do |stage| | ||
| block.action(stage) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "#[]" do | ||
| it "returns a config section" do | ||
| heroku_san.all.each do |app| | ||
| heroku_san[app].should be_a HerokuSan::Stage | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "#create_config" do | ||
| it "creates a new file using the example file" do | ||
| Dir.mktmpdir do |dir| | ||
| tmp_config_file = File.join dir, 'config.yml' | ||
| heroku_san = HerokuSan::Project.new(tmp_config_file) | ||
| FileUtils.should_receive(:cp).with(File.expand_path(template_config_file), tmp_config_file) | ||
| heroku_san.create_config.should be_true | ||
| end | ||
| end | ||
|
|
||
| it "does not overwrite an existing file" do | ||
| FileUtils.should_not_receive(:cp) | ||
| heroku_san.create_config.should be_false | ||
| end | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| require 'spec_helper' | ||
| require 'heroku/client' | ||
|
|
||
| describe HerokuSan::Stage do | ||
| include Git | ||
| subject { HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "bamboo-ree-1.8.7"})} | ||
|
|
||
| before do | ||
| @heroku_client = mock(Heroku::Client) | ||
| Heroku::Auth.stub(:client).and_return(@heroku_client) | ||
| end | ||
|
|
||
| context "initializes" do | ||
| subject { HerokuSan::Stage.new('production', | ||
| {"stack" => "cedar", | ||
| "app" => "awesomeapp-demo", | ||
| "tag" => "demo/*", | ||
| "config"=> {"BUNDLE_WITHOUT"=>"development:test"} | ||
| })} | ||
|
|
||
| its(:name) { should == 'production' } | ||
| its(:app) { should == 'awesomeapp-demo' } | ||
| its(:stack) { should == 'cedar' } | ||
| its(:tag) { should == "demo/*" } | ||
| its(:config) { should == {"BUNDLE_WITHOUT"=>"development:test"} } | ||
| its(:repo) { should == 'git@heroku.com:awesomeapp-demo.git' } | ||
| end | ||
|
|
||
| describe "#app" do | ||
| its(:app) { should == 'awesomeapp'} | ||
| context "blank app" do | ||
| subject { HerokuSan::Stage.new('production') } | ||
| it "should raise an error" do | ||
| expect { subject.app }.to raise_error(HerokuSan::MissingApp, /production: is missing the app: configuration value\./) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "#stack" do | ||
| it "returns the name of the stack from Heroku" do | ||
| subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp"}) | ||
| @heroku_client.should_receive(:list_stacks).with('awesomeapp'). | ||
| and_return { [{'name' => 'other'}, {'name' => 'the-one', 'current' => true}] } | ||
| subject.stack.should == 'the-one' | ||
| end | ||
|
|
||
| it "returns the stack name from the config if it is set there" do | ||
| subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "cedar"}) | ||
| subject.stack.should == 'cedar' | ||
| end | ||
| end | ||
|
|
||
| describe "#run" do | ||
| it "runs commands using the pre-cedar format" do | ||
| subject.should_receive(:sh).with("heroku run:rake foo bar bleh --app awesomeapp") | ||
| subject.run 'rake', 'foo bar bleh' | ||
| end | ||
| it "runs commands using the new cedar format" do | ||
| subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "cedar"}) | ||
| subject.should_receive(:sh).with("heroku run worker foo bar bleh --app awesomeapp") | ||
| subject.run 'worker', 'foo bar bleh' | ||
| end | ||
| end | ||
|
|
||
| describe "#deploy" do | ||
| it "deploys to heroku" do | ||
| subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, []) | ||
| subject.deploy | ||
| end | ||
|
|
||
| it "deploys with a custom sha" do | ||
| subject.should_receive(:git_push).with('deadbeef', subject.repo, []) | ||
| subject.deploy('deadbeef') | ||
| end | ||
|
|
||
| it "deploys with --force" do | ||
| subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, %w[--force]) | ||
| subject.deploy(nil, :force) | ||
| end | ||
|
|
||
| it "deploys with a custom sha & --force" do | ||
| subject.should_receive(:git_push).with('deadbeef', subject.repo, %w[--force]) | ||
| subject.deploy('deadbeef', :force) | ||
| end | ||
| end | ||
|
|
||
| describe "#migrate" do | ||
| it "runs rake db:migrate" do | ||
| subject.should_receive(:rake).with('db:migrate').and_return 'output:' | ||
| # @heroku_client.should_receive(:rake).with('awesomeapp', 'db:migrate').and_return "output:" | ||
| @heroku_client.should_receive(:ps_restart).with('awesomeapp').and_return "restarted" | ||
| subject.migrate.should == "restarted" | ||
| end | ||
| end | ||
|
|
||
| describe "#maintenance" do | ||
| it ":on" do | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :on) {'on'} | ||
| subject.maintenance(:on).should == 'on' | ||
| end | ||
|
|
||
| it ":off" do | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :off) {'off'} | ||
| subject.maintenance(:off).should == 'off' | ||
| end | ||
|
|
||
| it "otherwise raises an ArgumentError" do | ||
| expect do | ||
| subject.maintenance :busy | ||
| end.to raise_error ArgumentError, "Action #{:busy.inspect} must be one of (:on, :off)" | ||
| end | ||
|
|
||
| context "with a block" do | ||
| it "wraps it in a maitenance mode" do | ||
| reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).ordered | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :on).ordered | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :off).ordered | ||
| subject.maintenance do reactor.scram(:now) end | ||
| end | ||
| it "ensures that maintenance mode is turned off" do | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :on).ordered | ||
| reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).and_raise(RuntimeError) | ||
| @heroku_client.should_receive(:maintenance).with('awesomeapp', :off).ordered | ||
| expect { | ||
| subject.maintenance do reactor.scram(:now) end | ||
| }.to raise_error | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "#create" do | ||
| it "creates an app on heroku" do | ||
| @heroku_client.should_receive(:create).with('awesomeapp', {:stack => 'bamboo-ree-1.8.7'}) | ||
| subject.create | ||
| end | ||
| it "uses the default stack if none is given" do | ||
| subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp"}) | ||
| @heroku_client.should_receive(:create).with('awesomeapp') | ||
| subject.create | ||
| end | ||
| it "sends a nil app name if none is given (Heroku will generate one)" do | ||
| subject = HerokuSan::Stage.new('production', {"app" => nil}) | ||
| @heroku_client.should_receive(:create).with(nil).and_return('warm-ocean-9218') | ||
| subject.create.should == 'warm-ocean-9218' | ||
| end | ||
| end | ||
|
|
||
| describe "#sharing_add" do | ||
| it "add collaborators" do | ||
| subject.should_receive(:sh).with("heroku sharing:add email@example.com --app awesomeapp") | ||
| subject.sharing_add 'email@example.com' | ||
| end | ||
| end | ||
|
|
||
| describe "#sharing_remove" do | ||
| it "removes collaborators" do | ||
| subject.should_receive(:sh).with("heroku sharing:remove email@example.com --app awesomeapp") | ||
| subject.sharing_remove 'email@example.com' | ||
| end | ||
| end | ||
|
|
||
| describe "#long_config" do | ||
| it "returns the remote config" do | ||
| @heroku_client.should_receive(:config_vars).with('awesomeapp') { {'A' => 'one', 'B' => 'two'} } | ||
| subject.long_config.should == { 'A' => 'one', 'B' => 'two' } | ||
| end | ||
| end | ||
|
|
||
| describe "#restart" do | ||
| it "restarts an app" do | ||
| @heroku_client.should_receive(:ps_restart).with('awesomeapp').and_return "restarted" | ||
| subject.restart.should == 'restarted' | ||
| end | ||
| end | ||
|
|
||
| describe "#logs" do | ||
| it "returns log files" do | ||
| subject.should_receive(:sh).with("heroku logs --app awesomeapp") | ||
| subject.logs | ||
| end | ||
| it "tails log files" do | ||
| subject.should_receive(:sh).with("heroku logs --tail --app awesomeapp") | ||
| subject.logs(:tail) | ||
| end | ||
| end | ||
|
|
||
| describe "#push_config" do | ||
| it "updates the configuration settings on Heroku" do | ||
| subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {FOO: 'bar', DOG: 'emu'}}) | ||
| @heroku_client.should_receive(:add_config_vars).with('awesomeapp', {:FOO => 'bar', :DOG => 'emu'}).and_return("{}") | ||
| subject.push_config | ||
| end | ||
| it "pushes the options hash" do | ||
| @heroku_client.should_receive(:add_config_vars).with('awesomeapp', {:RACK_ENV => 'magic'}).and_return("{}") | ||
| subject.push_config(RACK_ENV: 'magic') | ||
| end | ||
| end | ||
|
|
||
| describe "#revision" do | ||
| it "returns the named remote revision for the stage" do | ||
| subject.should_receive(:git_revision).with(subject.repo) {"sha"} | ||
| subject.should_receive(:git_named_rev).with('sha') {"sha production/123456"} | ||
| subject.revision.should == 'sha production/123456' | ||
| end | ||
| it "returns nil if the stage has never been deployed" do | ||
| subject.should_receive(:git_revision).with(subject.repo) {nil} | ||
| subject.should_receive(:git_named_rev).with(nil) {''} | ||
| subject.revision.should == '' | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| require 'bundler' | ||
| Bundler.setup | ||
|
|
||
| SPEC_ROOT = File.dirname(__FILE__) | ||
|
|
||
| # Requires supporting ruby files with custom matchers and macros, etc, | ||
| # in spec/support/ and its subdirectories. | ||
| Dir[File.join(SPEC_ROOT, "support/**/*.rb")].each {|f| require f} | ||
|
|
||
| RSpec.configure do |config| | ||
| # == Mock Framework | ||
| # | ||
| # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: | ||
| # | ||
| # config.mock_with :mocha | ||
| # config.mock_with :flexmock | ||
| # config.mock_with :rr | ||
| config.mock_with :rspec | ||
| end | ||
|
|
||
| require File.join(SPEC_ROOT, '/../lib/heroku_san') |