diff --git a/lib/kumade.rb b/lib/kumade.rb index 21eda2c..1b45869 100644 --- a/lib/kumade.rb +++ b/lib/kumade.rb @@ -13,6 +13,7 @@ module Kumade autoload :NoopPackager, "kumade/packagers/noop_packager" autoload :PackagerList, "kumade/packager_list" autoload :RakeTaskRunner, "kumade/rake_task_runner" + autoload :CommandLine, "kumade/command_line" def self.configuration @@configuration ||= Configuration.new diff --git a/lib/kumade/base.rb b/lib/kumade/base.rb index f3870f8..8853a31 100644 --- a/lib/kumade/base.rb +++ b/lib/kumade/base.rb @@ -6,23 +6,6 @@ def initialize super() end - def run_or_error(command, error_message) - say_status(:run, command) - if ! Kumade.configuration.pretending? - error(error_message) unless run(command) - end - end - - def run(command) - line = Cocaine::CommandLine.new(command) - begin - line.run - true - rescue Cocaine::ExitStatusError => e - false - end - end - def error(message) say("==> ! #{message}", :red) raise Kumade::DeploymentError.new(message) diff --git a/lib/kumade/command_line.rb b/lib/kumade/command_line.rb new file mode 100644 index 0000000..a0537b2 --- /dev/null +++ b/lib/kumade/command_line.rb @@ -0,0 +1,41 @@ +require 'cocaine' + +module Kumade + class CommandFailedError < RuntimeError + end + + class CommandLine < Base + def initialize(command_to_run) + super() + @command_line = Cocaine::CommandLine.new(command_to_run) + end + + def run_or_error(error_message = nil) + if run_with_status + true + else + error(error_message) + end + end + + def run_with_status + say_status(:run, command) + Kumade.configuration.pretending? || run + end + + def run + begin + @command_line.run + true + rescue Cocaine::ExitStatusError, Cocaine::CommandNotFoundError + false + end + end + + private + + def command + @command_line.command + end + end +end diff --git a/lib/kumade/git.rb b/lib/kumade/git.rb index 8ffeddd..46406e1 100644 --- a/lib/kumade/git.rb +++ b/lib/kumade/git.rb @@ -20,25 +20,27 @@ def push(branch, remote = 'origin', force = false) command << remote command << branch command = command.join(" ") - run_or_error(command, "Failed to push #{branch} -> #{remote}") + + command_line = CommandLine.new(command) + command_line.run_or_error("Failed to push #{branch} -> #{remote}") success("Pushed #{branch} -> #{remote}") end def create(branch) - unless branch_exist?(branch) - run_or_error("git branch #{branch}", "Failed to create #{branch}") + unless has_branch?(branch) + CommandLine.new("git branch #{branch}").run_or_error("Failed to create #{branch}") end end def delete(branch_to_delete, branch_to_checkout) - run_or_error("git checkout #{branch_to_checkout} && git branch -D #{branch_to_delete}", - "Failed to clean up #{branch_to_delete} branch") + command_line = CommandLine.new("git checkout #{branch_to_checkout} && git branch -D #{branch_to_delete}") + command_line.run_or_error("Failed to clean up #{branch_to_delete} branch") end def add_and_commit_all_in(dir, branch, commit_message, success_output, error_output) - run_or_error "git checkout -b #{branch} && git add -f #{dir} && git commit -m '#{commit_message}'", - "Cannot deploy: #{error_output}" - success success_output + command_line = CommandLine.new("git checkout -b #{branch} && git add -f #{dir} && git commit -m '#{commit_message}'") + command_line.run_or_error("Cannot deploy: #{error_output}") + success(success_output) end def current_branch @@ -54,7 +56,7 @@ def remote_exists?(remote_name) end def dirty? - !run("git diff --exit-code") + ! CommandLine.new("git diff --exit-code").run end def ensure_clean_git @@ -65,8 +67,10 @@ def ensure_clean_git end end - def branch_exist?(branch) - run("git show-ref #{branch}") + private + + def has_branch?(branch) + CommandLine.new("git show-ref #{branch}").run end end end diff --git a/lib/kumade/heroku.rb b/lib/kumade/heroku.rb index 8057706..d9613c7 100644 --- a/lib/kumade/heroku.rb +++ b/lib/kumade/heroku.rb @@ -26,21 +26,29 @@ def delete_deploy_branch end def heroku(command) - heroku_command = if cedar? - "bundle exec heroku run" - else - "bundle exec heroku" - end - run_or_error("#{heroku_command} #{command} --remote #{Kumade.configuration.environment}", - "Failed to run #{command} on Heroku") + full_heroku_command = "#{bundle_exec_heroku} #{command} --remote #{Kumade.configuration.environment}" + command_line = CommandLine.new(full_heroku_command) + command_line.run_or_error("Failed to run #{command} on Heroku") end def cedar? return @cedar unless @cedar.nil? - @cedar = Cocaine::CommandLine.new("bundle exec heroku stack --remote #{Kumade.configuration.environment}").run.split("\n").grep(/\*/).any? do |line| + command_line = CommandLine.new("bundle exec heroku stack --remote #{Kumade.configuration.environment}") + + @cedar = command_line.run_or_error.split("\n").grep(/\*/).any? do |line| line.include?("cedar") end end + + private + + def bundle_exec_heroku + if cedar? + "bundle exec heroku run" + else + "bundle exec heroku" + end + end end end diff --git a/spec/kumade/base_spec.rb b/spec/kumade/base_spec.rb index cfccbdc..3406960 100644 --- a/spec/kumade/base_spec.rb +++ b/spec/kumade/base_spec.rb @@ -15,85 +15,3 @@ STDOUT.should have_received(:puts).with(regexp_matches(/I'm an error!/)) end end - -describe Kumade::Base, "#run_or_error" do - let(:command) { "dummy command" } - let(:error_message) { "dummy error message" } - - before do - STDOUT.stubs(:puts) - end - - context "when pretending" do - before do - Kumade.configuration.pretending = true - subject.stubs(:run) - end - - it "does not run the command" do - subject.run_or_error("dummy command", "dummy error message") - - subject.should have_received(:run).never - STDOUT.should have_received(:puts).with(regexp_matches(/#{command}/)) - end - end - - context "when not pretending" do - context "when it runs successfully" do - before do - Cocaine::CommandLine.stubs(:new).returns(stub(:run)) - end - - it "does not print an error" do - subject.run_or_error(command, error_message) - - STDOUT.should have_received(:puts).with(regexp_matches(/#{error_message}/)).never - end - end - - context "when it does not run successfully " do - let(:failing_command_line) { stub("Failing Cocaine::CommandLine") } - - before do - subject.stubs(:error) - failing_command_line.stubs(:run).raises(Cocaine::ExitStatusError) - Cocaine::CommandLine.stubs(:new).returns(failing_command_line) - end - - it "prints an error message" do - subject.run_or_error(command, error_message) - - subject.should have_received(:error).with(error_message) - end - end - end -end - -describe Kumade::Base, "#run" do - let(:command_line) { stub("Cocaine::CommandLine") } - let(:command) { "command" } - - before do - Cocaine::CommandLine.stubs(:new).with(command).returns(command_line) - end - - context "when not successful" do - before do - command_line.stubs(:run) - end - - it "returns true" do - subject.run(command).should == true - end - end - - context "when successful" do - before do - command_line.stubs(:run).raises(Cocaine::ExitStatusError) - end - - it "returns false" do - subject.run(command).should == false - end - end -end diff --git a/spec/kumade/command_line_spec.rb b/spec/kumade/command_line_spec.rb new file mode 100644 index 0000000..e905fc3 --- /dev/null +++ b/spec/kumade/command_line_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +describe Kumade::CommandLine, "#run_or_error" do + let(:command_line) { stub("Cocaine::CommandLine instance", :run => nil) } + subject { Kumade::CommandLine.new("echo") } + + before do + subject.stubs(:error) + subject.stubs(:say_status) + + Cocaine::CommandLine.stubs(:new).returns(command_line) + end + + context "when pretending" do + before do + Kumade.configuration.pretending = true + end + + it "does not run the command" do + subject.run_or_error + + command_line.should have_received(:run).never + end + end + + context "when successful" do + before do + Kumade.configuration.pretending = false + command_line.stubs(:run => true) + end + + it "returns true" do + subject.run_or_error.should be_true + end + end + + context "when unsuccessful" do + subject { Kumade::CommandLine.new("BAD COMMAND") } + before do + Kumade.configuration.pretending = false + end + + it "prints an error message" do + subject.run_or_error("something bad") + + subject.should have_received(:error).with("something bad") + end + end +end + +describe Kumade::CommandLine, "#run_with_status" do + let(:command) { "echo" } + let(:command_line) { stub("Cocaine::CommandLine instance", :run => nil, :command => command) } + subject { Kumade::CommandLine.new(command) } + + before do + Cocaine::CommandLine.stubs(:new).returns(command_line) + subject.stubs(:say_status) + end + + it "prints the command" do + subject.run_with_status + + subject.should have_received(:say_status).with(:run, "echo").once + end + + context "when pretending" do + before { Kumade.configuration.pretending = true } + + it "does not run the command" do + subject.run_with_status + + command_line.should have_received(:run).never + end + + it "returns true" do + subject.run_with_status.should be_true + end + end + + context "when not pretending" do + before { Kumade.configuration.pretending = false } + + it "runs the command" do + subject.run_with_status + + command_line.should have_received(:run).once + end + end +end + +describe Kumade::CommandLine, "#run" do + context "when successful" do + subject { Kumade::CommandLine.new("echo") } + + it "returns true" do + subject.run.should == true + end + end + + context "when unsuccessful" do + let(:bad_command) { "grep FAKE NOT_A_FILE" } + subject { Kumade::CommandLine.new("#{bad_command} 2>/dev/null") } + + it "returns false" do + subject.run.should be_false + end + end +end diff --git a/spec/kumade/git_spec.rb b/spec/kumade/git_spec.rb index 2e30163..73936e4 100644 --- a/spec/kumade/git_spec.rb +++ b/spec/kumade/git_spec.rb @@ -2,44 +2,21 @@ describe Kumade::Git, "#heroku_remote?" do context "when the environment is a Heroku repository" do - let(:environment) { 'staging' } + include_context "with Heroku environment" - before do - force_add_heroku_remote(environment) - Kumade.configuration.environment = environment - end - - after { remove_remote(environment) } - - its(:heroku_remote?) { should == true } + it { should be_heroku_remote } end context "when the environment is a Heroku repository managed with heroku-accounts" do - let(:another_heroku_environment) { 'another_staging' } - let(:another_heroku_url) { 'git@heroku.work:my-app.git' } + include_context "with Heroku-accounts environment" - before do - force_add_heroku_remote(another_heroku_environment) - Kumade.configuration.environment = another_heroku_environment - end - - after { remove_remote(another_heroku_environment) } - - its(:heroku_remote?) { should == true } + it { should be_heroku_remote } end context "when the environment is not a Heroku repository" do - let(:not_a_heroku_env) { 'fake_heroku' } - let(:not_a_heroku_url) { 'git@github.com:gabebw/kumade.git' } - - before do - `git remote add #{not_a_heroku_env} #{not_a_heroku_url}` - Kumade.configuration.environment = not_a_heroku_env - end - - after { remove_remote(not_a_heroku_env) } + include_context "with non-Heroku environment" - its(:heroku_remote?) { should == false } + it { should_not be_heroku_remote } end end @@ -63,56 +40,188 @@ end end -describe Kumade::Git, "#branch_exist?" do - let(:command_line) { mock("Cocaine::CommandLine") } - let(:branch) { "branch" } +describe Kumade::Git, "#push" do + let(:branch) { 'branch' } + let(:remote) { 'my-remote' } + let(:command_line) { stub("Kumade::CommandLine instance", :run_or_error => true) } before do - command_line.stubs(:run) - Cocaine::CommandLine.expects(:new).with("git show-ref #{branch}").returns(command_line) + Kumade::CommandLine.stubs(:new => command_line) + subject.stubs(:success) + end + + it "pushes to the correct remote" do + subject.push(branch, remote) + Kumade::CommandLine.should have_received(:new).with("git push #{remote} #{branch}") + command_line.should have_received(:run_or_error).once + end + + it "can force push" do + subject.push(branch, remote, true) + Kumade::CommandLine.should have_received(:new).with("git push -f #{remote} #{branch}") + command_line.should have_received(:run_or_error).once + end + + it "prints a success message" do + subject.push(branch, remote) + subject.should have_received(:success).with("Pushed #{branch} -> #{remote}") + end +end + +describe Kumade::Git, "#create" do + let(:branch) { "my-new-branch" } + it "creates a branch" do + subject.create(branch) + system("git show-ref #{branch}").should be_true end - it "returns true when the branch exists" do - subject.branch_exist?("branch").should be_true + context "when the branch already exists" do + before do + subject.create(branch) + subject.stubs(:error) + end - command_line.should have_received(:run) + it "does not error" do + subject.create(branch) + subject.should have_received(:error).never + end end +end - it "returns false if the branch doesn't exist" do - command_line.stubs(:run).raises(Cocaine::ExitStatusError) +describe Kumade::Git, "#delete" do + let(:branch_to_delete) { 'branch_to_delete' } + let(:branch_to_checkout) { 'branch_to_checkout' } + + before do + subject.create(branch_to_delete) + subject.create(branch_to_checkout) + end - subject.branch_exist?("branch").should be_false + it "switches to a branch" do + subject.delete(branch_to_delete, branch_to_checkout) + subject.current_branch.should == branch_to_checkout + end - command_line.should have_received(:run) + it "deletes a branch" do + subject.delete(branch_to_delete, branch_to_checkout) + `git show-ref #{branch_to_delete}`.strip.should be_empty end end -describe Kumade::Git, "#dirty?" do - context "when dirty" do - let(:failing_command_line) { mock("CommandLine instance") } +describe Kumade::Git, "#add_and_commit_all_in" do + let(:branch) { 'branch' } + let(:directory) { 'assets' } + let(:commit_message) { 'Committed some assets' } + + before do + Dir.mkdir(directory) + Dir.chdir(directory) do + File.open('new-file', 'w') do |f| + f.write('some content') + end + end + + subject.stubs(:success) + end + it "switches to the given branch" do + subject.add_and_commit_all_in(directory, branch, commit_message, 'success', 'error') + subject.current_branch.should == branch + end + + it "uses the given commit message" do + subject.add_and_commit_all_in(directory, branch, commit_message, 'success', 'error') + `git log -n1 --pretty=format:%s`.should == commit_message + end + + it "commits everything in the dir" do + subject.add_and_commit_all_in(directory, branch, commit_message, 'success', 'error') + subject.should_not be_dirty + end + + it "prints a success message" do + subject.add_and_commit_all_in(directory, branch, commit_message, 'success', 'error') + subject.should have_received(:success).with('success') + end +end + +describe Kumade::Git, "#current_branch" do + it "returns the current branch" do + subject.current_branch.should == 'master' + `git checkout -b new-branch` + subject.current_branch.should == 'new-branch' + end +end + +describe Kumade::Git, "#remote_exists?" do + context "when pretending" do + before { Kumade.configuration.pretending = true } + it "returns true no matter what" do + subject.remote_exists?('not-a-remote').should be_true + end + end + + context "when not pretending" do + let(:good_remote) { 'good-remote' } + let(:bad_remote) { 'bad-remote' } before do - failing_command_line.stubs(:run).raises(Cocaine::ExitStatusError) + Kumade.configuration.pretending = false + force_add_heroku_remote(good_remote) + end - Cocaine::CommandLine.expects(:new). - with("git diff --exit-code"). - returns(failing_command_line) + it "returns true if the remote exists" do + subject.remote_exists?(good_remote).should be_true end - it "returns true" do - subject.dirty?.should == true + it "returns false if the remote does not exist" do + subject.remote_exists?(bad_remote).should be_false end end +end + +describe Kumade::Git, "#dirty?" do + context "when dirty" do + before { dirty_the_repo } + + it { should be_dirty } + end context "when clean" do + it { should_not be_dirty } + end +end + + +describe Kumade::Git, "#ensure_clean_git" do + before do + subject.stubs(:success => nil, :error => nil) + end + + context "when pretending" do before do - Cocaine::CommandLine.expects(:new). - with("git diff --exit-code"). - returns(stub("Successful CommandLine", :run => true)) + Kumade.configuration.pretending = true + dirty_the_repo end - it "returns false" do - subject.dirty?.should == false + it "prints a success message" do + subject.ensure_clean_git + subject.should have_received(:success).with("Git repo is clean") + end + end + + context "when repo is clean" do + it "prints a success message" do + subject.ensure_clean_git + subject.should have_received(:success).with("Git repo is clean") + end + end + + context "when repo is dirty" do + before { dirty_the_repo } + + it "prints an error message" do + subject.ensure_clean_git + subject.should have_received(:error).with("Cannot deploy: repo is not clean.") end end end diff --git a/spec/kumade/heroku_spec.rb b/spec/kumade/heroku_spec.rb index 81f6bd0..e849829 100644 --- a/spec/kumade/heroku_spec.rb +++ b/spec/kumade/heroku_spec.rb @@ -59,9 +59,10 @@ end describe Kumade::Heroku, "#heroku" do - let(:command_line_instance) { stub("Cocaine::CommandLine instance", :run => true) } + let(:command_instance) { stub("Kumade::CommandLine instance", :run_or_error => true) } before do + Kumade::CommandLine.stubs(:new => command_instance) STDOUT.stubs(:puts) end @@ -69,11 +70,10 @@ include_context "when on Cedar" it "runs commands with `run`" do - Cocaine::CommandLine.expects(:new). - with(regexp_matches(/bundle exec heroku run/)). - returns(command_line_instance) - subject.heroku("rake") + + Kumade::CommandLine.should have_received(:new).with(regexp_matches(/bundle exec heroku run rake/)).once + command_instance.should have_received(:run_or_error).once end end @@ -81,11 +81,10 @@ include_context "when not on Cedar" it "runs commands without `run`" do - Cocaine::CommandLine.expects(:new). - with(regexp_matches(/bundle exec heroku rake/)). - returns(command_line_instance) - subject.heroku("rake") + + Kumade::CommandLine.should have_received(:new).with(regexp_matches(/bundle exec heroku rake/)).once + command_instance.should have_received(:run_or_error).once end end end @@ -109,13 +108,16 @@ end describe Kumade::Heroku, "#delete_deploy_branch" do - before { STDOUT.stubs(:puts) } + let(:command_instance) { stub("Kumade::CommandLine instance", :run_or_error => true) } - it "deletes the deploy branch" do - Cocaine::CommandLine.expects(:new). - with("git checkout master && git branch -D deploy"). - returns(stub(:run => true)) + before do + Kumade::CommandLine.stubs(:new => command_instance) + end + it "deletes the deploy branch" do subject.delete_deploy_branch + + Kumade::CommandLine.should have_received(:new).with("git checkout master && git branch -D deploy").once + command_instance.should have_received(:run_or_error).once end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4745c95..fd44db4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -30,6 +30,8 @@ def remove_remote(remote_name) config.include Rake::DSL if defined?(Rake::DSL) config.include GitRemoteHelpers + config.include GitHelpers + config.include EnvironmentHelpers config.include Aruba::Api config.around do |example| diff --git a/spec/support/environments.rb b/spec/support/environments.rb new file mode 100644 index 0000000..834f97e --- /dev/null +++ b/spec/support/environments.rb @@ -0,0 +1,32 @@ +module EnvironmentHelpers + shared_context "with Heroku environment" do + let(:environment) { 'staging' } + before do + force_add_heroku_remote(environment) + Kumade.configuration.environment = environment + end + + after { remove_remote(environment) } + end + + shared_context "with Heroku-accounts environment" do + let(:environment) { 'heroku-accounts' } + let(:heroku_url) { 'git@heroku.work:my-app.git' } + before do + `git remote add #{environment} #{heroku_url}` + Kumade.configuration.environment = environment + end + after { remove_remote(environment) } + end + + shared_context "with non-Heroku environment" do + let(:environment) { 'not-heroku' } + let(:not_a_heroku_url) { 'git@github.com:gabebw/kumade.git' } + + before do + `git remote add #{environment} #{not_a_heroku_url}` + Kumade.configuration.environment = environment + end + after { remove_remote(environment) } + end +end diff --git a/spec/support/git.rb b/spec/support/git.rb new file mode 100644 index 0000000..8c096c8 --- /dev/null +++ b/spec/support/git.rb @@ -0,0 +1,5 @@ +module GitHelpers + def dirty_the_repo + `echo dirty_it_up > .gitkeep` + end +end diff --git a/spec/support/heroku.rb b/spec/support/heroku.rb index b2613bb..f4a7501 100644 --- a/spec/support/heroku.rb +++ b/spec/support/heroku.rb @@ -1,12 +1,12 @@ shared_context "when on Cedar" do - let(:command_line) { mock("Cocaine::CommandLine") } + let(:command_line) { mock("Kumade::CommandLine instance") } before do - Cocaine::CommandLine.expects(:new). + Kumade::CommandLine.expects(:new). with("bundle exec heroku stack --remote staging"). returns(command_line) - command_line.expects(:run).returns(%{ + command_line.expects(:run_or_error).returns(%{ aspen-mri-1.8.6 bamboo-mri-1.9.2 bamboo-ree-1.8.7 @@ -16,14 +16,14 @@ end shared_context "when not on Cedar" do - let(:command_line) { mock("Cocaine::CommandLine") } + let(:command_line) { mock("Kumade::CommandLine") } before do - Cocaine::CommandLine.expects(:new). + Kumade::CommandLine.expects(:new). with("bundle exec heroku stack --remote staging"). returns(command_line) - command_line.expects(:run).returns(%{ + command_line.expects(:run_or_error).returns(%{ aspen-mri-1.8.6 * bamboo-mri-1.9.2 bamboo-ree-1.8.7