diff --git a/lib/foreman/cli.rb b/lib/foreman/cli.rb index 7dc1f3c..eb7cd2a 100644 --- a/lib/foreman/cli.rb +++ b/lib/foreman/cli.rb @@ -40,22 +40,11 @@ class Foreman::CLI < Thor method_option :port, :type => :numeric, :aliases => "-p" method_option :user, :type => :string, :aliases => "-u" method_option :template, :type => :string, :aliases => "-t" - method_option :concurrency, :type => :string, :aliases => "-c", - :banner => '"alpha=5,bar=3"' + method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"' def export(format, location=nil) check_procfile! - - begin - require "foreman/export/#{ format.tr('-', '_') }" - classy_format = classify(format) - formatter = constantize("Foreman::Export::#{ classy_format }") - rescue NameError => ex - error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })." - rescue LoadError => ex - error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')." - end - + formatter = Foreman::Export.formatter(format) formatter.new(location, engine, options).export rescue Foreman::Export::Exception => ex error ex.message @@ -65,7 +54,7 @@ class Foreman::CLI < Thor def check error "no processes defined" unless engine.procfile.entries.length > 0 - display "valid procfile detected (#{engine.procfile.process_names.join(', ')})" + puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})" end desc "run COMMAND", "Run a command using your application's environment" @@ -95,19 +84,11 @@ private ###################################################################### options[:procfile] || "Procfile" end - def display(message) - puts message - end - def error(message) puts "ERROR: #{message}" exit 1 end - def procfile_exists?(procfile) - File.exist?(procfile) - end - def options original_options = super return original_options unless File.exists?(".foreman") diff --git a/lib/foreman/export.rb b/lib/foreman/export.rb index 8a0e0b3..df31d66 100644 --- a/lib/foreman/export.rb +++ b/lib/foreman/export.rb @@ -1,9 +1,30 @@ require "foreman" +require "foreman/helpers" module Foreman::Export + extend Foreman::Helpers + class Exception < ::Exception; end + + def self.formatter(format) + begin + require "foreman/export/#{ format.tr('-', '_') }" + classy_format = classify(format) + formatter = constantize("Foreman::Export::#{ classy_format }") + rescue NameError => ex + error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })." + rescue LoadError => ex + error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')." + end + end + + def self.error(message) + raise Foreman::Export::Exception.new(message) + end + end + require "foreman/export/base" require "foreman/export/inittab" require "foreman/export/upstart" diff --git a/lib/foreman/export/inittab.rb b/lib/foreman/export/inittab.rb index de04e30..31b8036 100644 --- a/lib/foreman/export/inittab.rb +++ b/lib/foreman/export/inittab.rb @@ -24,12 +24,12 @@ class Foreman::Export::Inittab < Foreman::Export::Base inittab = inittab.join("\n") + "\n" - if fname + if location == "-" + puts inittab + else FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}" FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}" - write_file(fname, inittab) - else - puts inittab + write_file(location, inittab) end end diff --git a/lib/foreman/helpers.rb b/lib/foreman/helpers.rb index 4b5625d..95b6e17 100644 --- a/lib/foreman/helpers.rb +++ b/lib/foreman/helpers.rb @@ -6,9 +6,8 @@ module Foreman::Helpers # classify('job-name') # => 'JobName' def classify(dashed_word) dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join - end + end # Tries to find a constant with the name specified in the argument string: - # Tries to find a constant with the name specified in the argument string: # # constantize("Module") # => Module # constantize("Test::Unit") # => Test::Unit @@ -28,10 +27,6 @@ module Foreman::Helpers def constantize(camel_cased_word) camel_cased_word = camel_cased_word.to_s - if camel_cased_word.include?('-') - camel_cased_word = classify(camel_cased_word) - end - names = camel_cased_word.split('::') names.shift if names.empty? || names.first.empty? @@ -47,4 +42,4 @@ module Foreman::Helpers end constant end -end \ No newline at end of file +end diff --git a/spec/foreman/cli_spec.rb b/spec/foreman/cli_spec.rb index 430fa84..a18d982 100644 --- a/spec/foreman/cli_spec.rb +++ b/spec/foreman/cli_spec.rb @@ -27,6 +27,15 @@ describe "Foreman::CLI", :fakefs do describe "export" do describe "options" do + it "uses .foreman" do + write_procfile + File.open(".foreman", "w") { |f| f.puts "concurrency: alpha=2" } + mock_export = mock(Foreman::Export::Upstart) + mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "concurrency" => "alpha=2" }) { mock_export } + mock_export.export + foreman %{ export upstart /upstart } + end + it "respects --env" do write_procfile write_env("envfile") @@ -49,10 +58,18 @@ describe "Foreman::CLI", :fakefs do describe "with a Procfile" do before(:each) { write_procfile } - describe "with an invalid formatter" do + describe "with a formatter with a generic error" do + before do + mock(Foreman::Export).formatter("errorful") { Class.new(Foreman::Export::Base) do + def export + raise Foreman::Export::Exception.new("foo") + end + end } + end + it "prints an error" do - mock_error(subject, "Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do - subject.export("invalidformatter") + mock_error(subject, "foo") do + subject.export("errorful") end end end @@ -76,7 +93,7 @@ describe "Foreman::CLI", :fakefs do before { write_procfile } it "displays the jobs" do - mock(subject).display("valid procfile detected (alpha, bravo)") + mock(subject).puts("valid procfile detected (alpha, bravo)") subject.check end end diff --git a/spec/foreman/export/base_spec.rb b/spec/foreman/export/base_spec.rb new file mode 100644 index 0000000..c0b1333 --- /dev/null +++ b/spec/foreman/export/base_spec.rb @@ -0,0 +1,22 @@ +require "spec_helper" +require "foreman/export/base" + +describe "Foreman::Export::Base" do + let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") } + let(:location) { "/tmp/init" } + let(:engine) { Foreman::Engine.new(procfile) } + let(:subject) { Foreman::Export::Base.new(location, engine) } + + it "has a say method for displaying info" do + mock(subject).puts("[foreman export] foo") + subject.send(:say, "foo") + end + + it "export needs to be overridden" do + lambda { subject.export }.should raise_error("export method must be overridden") + end + + it "raises errors as a Foreman::Export::Exception" do + lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo") + end +end diff --git a/spec/foreman/export/bluepill_spec.rb b/spec/foreman/export/bluepill_spec.rb index 7ced939..d6074b6 100644 --- a/spec/foreman/export/bluepill_spec.rb +++ b/spec/foreman/export/bluepill_spec.rb @@ -17,7 +17,14 @@ describe Foreman::Export::Bluepill, :fakefs do normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill")) end - context "with concurrency" do + it "cleans up if exporting into an existing dir" do + mock(FileUtils).rm("/tmp/init/app.pill") + + bluepill.export + bluepill.export + end + + context "with concurrency" do let(:options) { Hash[:concurrency => "alpha=2"] } it "exports to the filesystem with concurrency" do diff --git a/spec/foreman/export/inittab_spec.rb b/spec/foreman/export/inittab_spec.rb new file mode 100644 index 0000000..30c921b --- /dev/null +++ b/spec/foreman/export/inittab_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" +require "foreman/engine" +require "foreman/export/inittab" +require "tmpdir" + +describe Foreman::Export::Inittab, :fakefs do + let(:location) { "/tmp/inittab" } + let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") } + let(:location) { "/tmp/inittab" } + let(:engine) { Foreman::Engine.new(procfile) } + let(:options) { Hash.new } + let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) } + + before(:each) { load_export_templates_into_fakefs("inittab") } + before(:each) { stub(inittab).say } + + it "exports to the filesystem" do + inittab.export + File.read("/tmp/inittab").should == example_export_file("inittab/inittab.default") + end + + context "to stdout" do + let(:location) { "-" } + + it "exports to stdout" do + mock(inittab).puts example_export_file("inittab/inittab.default") + inittab.export + end + end + + context "with concurrency" do + let(:options) { Hash[:concurrency => "alpha=2"] } + + it "exports to the filesystem with concurrency" do + inittab.export + File.read("/tmp/inittab").should == example_export_file("inittab/inittab.concurrency") + end + end + +end diff --git a/spec/foreman/export/upstart_spec.rb b/spec/foreman/export/upstart_spec.rb index fb7b5d4..62f21dd 100644 --- a/spec/foreman/export/upstart_spec.rb +++ b/spec/foreman/export/upstart_spec.rb @@ -22,6 +22,17 @@ describe Foreman::Export::Upstart, :fakefs do File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf") end + it "cleans up if exporting into an existing dir" do + mock(FileUtils).rm("/tmp/init/app.conf") + mock(FileUtils).rm("/tmp/init/app-alpha.conf") + mock(FileUtils).rm("/tmp/init/app-alpha-1.conf") + mock(FileUtils).rm("/tmp/init/app-bravo.conf") + mock(FileUtils).rm("/tmp/init/app-bravo-1.conf") + + upstart.export + upstart.export + end + context "with concurrency" do let(:options) { Hash[:concurrency => "alpha=2"] } diff --git a/spec/foreman/export_spec.rb b/spec/foreman/export_spec.rb index bcfd80a..2008dab 100644 --- a/spec/foreman/export_spec.rb +++ b/spec/foreman/export_spec.rb @@ -1,2 +1,24 @@ require "spec_helper" require "foreman/export" + +describe "Foreman::Export" do + subject { Foreman::Export } + + describe "with a formatter that doesn't declare the appropriate class" do + it "prints an error" do + mock(subject).require("foreman/export/invalidformatter") + mock_export_error("Unknown export format: invalidformatter (no class Foreman::Export::Invalidformatter).") do + subject.formatter("invalidformatter") + end + end + end + + describe "with an invalid formatter" do + + it "prints an error" do + mock_export_error("Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do + subject.formatter("invalidformatter") + end + end + end +end diff --git a/spec/foreman/helpers_spec.rb b/spec/foreman/helpers_spec.rb new file mode 100644 index 0000000..501ea12 --- /dev/null +++ b/spec/foreman/helpers_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" +require "foreman/helpers" + +describe "Foreman::Helpers" do + before do + module Foo + class Bar; end + end + end + + after do + Object.send(:remove_const, :Foo) + end + + subject { o = Object.new; o.extend(Foreman::Helpers); o } + + it "should classify words" do + subject.classify("foo").should == "Foo" + subject.classify("foo-bar").should == "FooBar" + end + + it "should constantize words" do + subject.constantize("Object").should == Object + subject.constantize("Foo::Bar").should == Foo::Bar + end +end diff --git a/spec/foreman_spec.rb b/spec/foreman_spec.rb index dfd0fbc..016e527 100644 --- a/spec/foreman_spec.rb +++ b/spec/foreman_spec.rb @@ -25,4 +25,10 @@ describe Foreman do ENV['FOO'].should == 'bar' end end + + describe "runner" do + it "should exist" do + File.exists?(Foreman.runner).should == true + end + end end diff --git a/spec/resources/export/inittab/inittab.concurrency b/spec/resources/export/inittab/inittab.concurrency new file mode 100644 index 0000000..9799fda --- /dev/null +++ b/spec/resources/export/inittab/inittab.concurrency @@ -0,0 +1,4 @@ +# ----- foreman app processes ----- +AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1' +AP02:4:respawn:/bin/su - app -c 'PORT=5001 ./alpha >> /var/log/app/alpha-2.log 2>&1' +# ----- end foreman app processes ----- diff --git a/spec/resources/export/inittab/inittab.default b/spec/resources/export/inittab/inittab.default new file mode 100644 index 0000000..0d5a094 --- /dev/null +++ b/spec/resources/export/inittab/inittab.default @@ -0,0 +1,4 @@ +# ----- foreman app processes ----- +AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1' +AP02:4:respawn:/bin/su - app -c 'PORT=5100 ./bravo >> /var/log/app/bravo-1.log 2>&1' +# ----- end foreman app processes ----- diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1f2f637..0dd6dd7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,20 @@ require "rubygems" + +require "simplecov" +SimpleCov.start do + add_filter "/spec/" +end + require "rspec" require "fakefs/safe" require "fakefs/spec_helpers" $:.unshift File.expand_path("../../lib", __FILE__) +def mock_export_error(message) + lambda { yield }.should raise_error(Foreman::Export::Exception, message) +end + def mock_error(subject, message) mock_exit do mock(subject).puts("ERROR: #{message}") @@ -37,9 +47,11 @@ def write_procfile(procfile="Procfile", alpha_env="") File.expand_path(procfile) end -def write_env(env=".env") +def write_env(env=".env", options={"FOO"=>"bar"}) File.open(env, "w") do |file| - file.puts "FOO=bar" + options.each do |key, val| + file.puts "#{key}=#{val}" + end end end