pulsar 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +282 -9
  4. data/circle.yml +56 -11
  5. data/lib/pulsar.rb +3 -1
  6. data/lib/pulsar/cli.rb +29 -4
  7. data/lib/pulsar/context_error.rb +7 -0
  8. data/lib/pulsar/interactors/add_applications.rb +5 -7
  9. data/lib/pulsar/interactors/cleanup.rb +1 -0
  10. data/lib/pulsar/interactors/clone_repository.rb +3 -8
  11. data/lib/pulsar/interactors/copy_environment_file.rb +15 -11
  12. data/lib/pulsar/interactors/copy_initial_repository.rb +2 -9
  13. data/lib/pulsar/interactors/create_capfile.rb +14 -9
  14. data/lib/pulsar/interactors/create_deploy_file.rb +3 -8
  15. data/lib/pulsar/interactors/create_run_dirs.rb +1 -0
  16. data/lib/pulsar/interactors/identify_repository_location.rb +2 -9
  17. data/lib/pulsar/interactors/identify_repository_type.rb +2 -8
  18. data/lib/pulsar/interactors/run_bundle_install.rb +3 -9
  19. data/lib/pulsar/interactors/run_capistrano.rb +4 -12
  20. data/lib/pulsar/organizers/{deploy.rb → task.rb} +1 -1
  21. data/lib/pulsar/validator.rb +33 -0
  22. data/lib/pulsar/version.rb +1 -1
  23. data/pulsar.gemspec +2 -2
  24. data/spec/features/task_spec.rb +187 -0
  25. data/spec/spec_helper.rb +1 -6
  26. data/spec/support/coverage_setup.rb +39 -0
  27. data/spec/units/cli/deploy_spec.rb +28 -6
  28. data/spec/units/cli/install_spec.rb +22 -0
  29. data/spec/units/cli/list_spec.rb +23 -1
  30. data/spec/units/cli/task_spec.rb +138 -0
  31. data/spec/units/cli/version_spec.rb +13 -0
  32. data/spec/units/interactors/copy_environment_file_spec.rb +25 -0
  33. data/spec/units/interactors/create_capfile_spec.rb +22 -0
  34. data/spec/units/interactors/run_capistrano_spec.rb +75 -15
  35. data/spec/units/organizers/{deploy_spec.rb → task_spec.rb} +1 -1
  36. data/spec/units/validator_spec.rb +31 -0
  37. metadata +40 -14
@@ -0,0 +1,7 @@
1
+ module Pulsar
2
+ class ContextError < StandardError
3
+ def to_s
4
+ backtrace ? backtrace.unshift(super).join("\n") : super
5
+ end
6
+ end
7
+ end
@@ -1,8 +1,10 @@
1
1
  module Pulsar
2
2
  class AddApplications
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!, :prepare_context
6
+ validate_context_for! :config_path
7
+ before :prepare_context
6
8
  after :validate_output!
7
9
 
8
10
  def call
@@ -10,7 +12,7 @@ module Pulsar
10
12
  context.applications[File.basename(app)] = stages_for(app)
11
13
  end
12
14
  rescue
13
- context.fail! error: $!.message
15
+ context_fail! $!.message
14
16
  end
15
17
 
16
18
  private
@@ -19,12 +21,8 @@ module Pulsar
19
21
  context.applications = {}
20
22
  end
21
23
 
22
- def validate_input!
23
- context.fail! if context.config_path.nil?
24
- end
25
-
26
24
  def validate_output!
27
- context.fail! error: "No application found on repository #{context.repository}" if context.applications.empty?
25
+ context_fail! "No application found on repository #{context.repository}" if context.applications.empty?
28
26
  end
29
27
 
30
28
  def each_application_path
@@ -1,6 +1,7 @@
1
1
  module Pulsar
2
2
  class Cleanup
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
6
  def call
6
7
  FileUtils.rm_rf(context.run_path)
@@ -1,8 +1,9 @@
1
1
  module Pulsar
2
2
  class CloneRepository
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!
6
+ validate_context_for! :config_path, :repository, :repository_type
6
7
 
7
8
  def call
8
9
  case context.repository_type
@@ -11,17 +12,11 @@ module Pulsar
11
12
  when :folder then copy_local_folder
12
13
  end
13
14
  rescue
14
- context.fail! error: $!.message
15
+ context_fail! $!.message
15
16
  end
16
17
 
17
18
  private
18
19
 
19
- def validate_input!
20
- context.fail! if context.config_path.nil? ||
21
- context.repository.nil? ||
22
- context.repository_type.nil?
23
- end
24
-
25
20
  def clone_git_repository
26
21
  cmd = 'git clone'
27
22
  opts = '--quiet --depth 1'
@@ -1,8 +1,11 @@
1
1
  module Pulsar
2
2
  class CopyEnvironmentFile
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!, :prepare_context
6
+ validate_context_for! :config_path, :cap_path, :application, :applications
7
+ before :validate_environment!
8
+ before :prepare_context
6
9
 
7
10
  def call
8
11
  env_file = "#{context.config_path}/apps/#{context.application}/#{context.environment}.rb"
@@ -10,7 +13,7 @@ module Pulsar
10
13
  FileUtils.mkdir_p(context.cap_deploy_path)
11
14
  FileUtils.cp(env_file, context.environment_file_path)
12
15
  rescue
13
- context.fail! error: $!.message
16
+ context_fail! $!.message
14
17
  end
15
18
 
16
19
  private
@@ -20,15 +23,16 @@ module Pulsar
20
23
  context.environment_file_path = "#{context.cap_deploy_path}/#{context.environment}.rb"
21
24
  end
22
25
 
23
- def validate_input!
24
- context.fail! if context.config_path.nil? ||
25
- context.cap_path.nil? ||
26
- context.application.nil? ||
27
- context.applications.nil? ||
28
- context.environment.nil?
29
- unless context.applications[context.application].include? context.environment
30
- context.fail! error: "The application #{context.application} does not have an environment called #{context.environment}"
31
- end
26
+ def validate_environment!
27
+ fail_on_missing_environment! unless environment_exist?
28
+ end
29
+
30
+ def environment_exist?
31
+ context.applications[context.application].include?(context.environment)
32
+ end
33
+
34
+ def fail_on_missing_environment!
35
+ context_fail! "The application #{context.application} does not have an environment called #{context.environment}"
32
36
  end
33
37
  end
34
38
  end
@@ -1,8 +1,7 @@
1
1
  module Pulsar
2
2
  class CopyInitialRepository
3
3
  include Interactor
4
-
5
- before :validate_input!
4
+ include Pulsar::Validator
6
5
 
7
6
  def call
8
7
  current_path = File.dirname(__FILE__)
@@ -10,13 +9,7 @@ module Pulsar
10
9
 
11
10
  FileUtils.cp_r(File.expand_path(initial_repo), context.directory)
12
11
  rescue
13
- context.fail! error: $!.message
14
- end
15
-
16
- private
17
-
18
- def validate_input!
19
- context.fail! if context.directory.nil?
12
+ context_fail! $!.message
20
13
  end
21
14
  end
22
15
  end
@@ -1,8 +1,10 @@
1
1
  module Pulsar
2
2
  class CreateCapfile
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!, :prepare_context
6
+ validate_context_for! :config_path, :cap_path, :application, :applications
7
+ before :validate_application!, :prepare_context
6
8
 
7
9
  def call
8
10
  default_capfile = "#{context.config_path}/apps/Capfile"
@@ -14,7 +16,7 @@ module Pulsar
14
16
  Rake.sh("cat #{app_capfile} >> #{context.capfile_path}") if File.exist?(app_capfile)
15
17
  Rake.sh("echo '#{import_tasks}' >> #{context.capfile_path}")
16
18
  rescue
17
- context.fail! error: $!.message
19
+ context_fail! $!.message
18
20
  end
19
21
 
20
22
  private
@@ -23,13 +25,16 @@ module Pulsar
23
25
  context.capfile_path = "#{context.cap_path}/Capfile"
24
26
  end
25
27
 
26
- def validate_input!
27
- context.fail! if context.config_path.nil? ||
28
- context.cap_path.nil? ||
29
- context.application.nil?
30
- unless context.applications.keys.include? context.application
31
- context.fail! error: "The application #{context.application} does not exist in your repository"
32
- end
28
+ def validate_application!
29
+ fail_on_missing_application! unless application_exists?
30
+ end
31
+
32
+ def application_exists?
33
+ context.applications.keys.include? context.application
34
+ end
35
+
36
+ def fail_on_missing_application!
37
+ context_fail! "The application #{context.application} does not exist in your repository"
33
38
  end
34
39
  end
35
40
  end
@@ -1,8 +1,9 @@
1
1
  module Pulsar
2
2
  class CreateDeployFile
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!, :prepare_context
6
+ before :prepare_context
6
7
 
7
8
  def call
8
9
  default_deploy = "#{context.config_path}/apps/deploy.rb"
@@ -13,7 +14,7 @@ module Pulsar
13
14
  Rake.sh("cat #{default_deploy} >> #{context.deploy_file_path}") if File.exist?(default_deploy)
14
15
  Rake.sh("cat #{app_deploy} >> #{context.deploy_file_path}") if File.exist?(app_deploy)
15
16
  rescue
16
- context.fail! error: $!.message
17
+ context_fail!$!.message
17
18
  end
18
19
 
19
20
  private
@@ -22,11 +23,5 @@ module Pulsar
22
23
  context.cap_config_path = "#{context.cap_path}/config"
23
24
  context.deploy_file_path = "#{context.cap_config_path}/deploy.rb"
24
25
  end
25
-
26
- def validate_input!
27
- context.fail! if context.config_path.nil? ||
28
- context.cap_path.nil? ||
29
- context.application.nil?
30
- end
31
26
  end
32
27
  end
@@ -1,6 +1,7 @@
1
1
  module Pulsar
2
2
  class CreateRunDirs
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
6
  def call
6
7
  context.timestamp = Time.now.to_f
@@ -1,8 +1,7 @@
1
1
  module Pulsar
2
2
  class IdentifyRepositoryLocation
3
3
  include Interactor
4
-
5
- before :validate_input!
4
+ include Pulsar::Validator
6
5
 
7
6
  def call
8
7
  context.repository_location = if File.exist?(context.repository)
@@ -11,13 +10,7 @@ module Pulsar
11
10
  :remote
12
11
  end
13
12
  rescue
14
- context.fail! error: $!.message
15
- end
16
-
17
- private
18
-
19
- def validate_input!
20
- context.fail! if context.repository.nil?
13
+ context_fail! $!.message
21
14
  end
22
15
  end
23
16
  end
@@ -1,8 +1,9 @@
1
1
  module Pulsar
2
2
  class IdentifyRepositoryType
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!
6
+ validate_context_for! :repository, :repository_location
6
7
 
7
8
  def call
8
9
  case context.repository_location
@@ -11,17 +12,10 @@ module Pulsar
11
12
  when :remote
12
13
  context.repository_type = github_repository? ? :github : :git
13
14
  end
14
- rescue
15
- context.fail! error: $!.message
16
15
  end
17
16
 
18
17
  private
19
18
 
20
- def validate_input!
21
- context.fail! if context.repository.nil?
22
- context.fail! if context.repository_location.nil?
23
- end
24
-
25
19
  def github_repository?
26
20
  context.repository =~ %r{^[\w-]+\/[\w-]+$}
27
21
  end
@@ -1,8 +1,9 @@
1
1
  module Pulsar
2
2
  class RunBundleInstall
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!
6
+ validate_context_for! :config_path, :bundle_path
6
7
 
7
8
  def call
8
9
  gemfile_env = "BUNDLE_GEMFILE=#{context.config_path}/Gemfile"
@@ -15,14 +16,7 @@ module Pulsar
15
16
  Rake.sh("#{bundle_cmd}#{out_redir}")
16
17
  end
17
18
  rescue
18
- context.fail!
19
- end
20
-
21
- private
22
-
23
- def validate_input!
24
- context.fail! if context.config_path.nil? ||
25
- context.bundle_path.nil?
19
+ context_fail! $!.message
26
20
  end
27
21
  end
28
22
  end
@@ -1,8 +1,9 @@
1
1
  module Pulsar
2
2
  class RunCapistrano
3
3
  include Interactor
4
+ include Pulsar::Validator
4
5
 
5
- before :validate_input!
6
+ validate_context_for! :cap_path, :config_path, :bundle_path, :environment
6
7
 
7
8
  def call
8
9
  Dir.chdir(context.cap_path) do
@@ -10,21 +11,12 @@ module Pulsar
10
11
  bundle_env = "BUNDLE_PATH=#{context.bundle_path}"
11
12
  cap_opts = ENV['DRY_RUN'] ? '--dry-run ' : nil
12
13
  out_redir = ENV['DRY_RUN'] ? '> /dev/null 2>&1' : nil
13
- cap_cmd = "bundle exec cap #{cap_opts}#{context.environment} deploy"
14
+ cap_cmd = "bundle exec cap #{cap_opts}#{context.environment} #{context.task}"
14
15
 
15
16
  Rake.sh("#{gemfile_env} #{bundle_env} #{cap_cmd}#{out_redir}")
16
17
  end
17
18
  rescue
18
- context.fail! error: $!.message
19
- end
20
-
21
- private
22
-
23
- def validate_input!
24
- context.fail! if context.cap_path.nil? ||
25
- context.config_path.nil? ||
26
- context.bundle_path.nil? ||
27
- context.environment.nil?
19
+ context_fail! $!.message
28
20
  end
29
21
  end
30
22
  end
@@ -1,5 +1,5 @@
1
1
  module Pulsar
2
- class Deploy
2
+ class Task
3
3
  include Interactor::Organizer
4
4
 
5
5
  organize IdentifyRepositoryLocation,
@@ -0,0 +1,33 @@
1
+ module Pulsar
2
+ module Validator
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ klass.before :validate_context!
6
+ end
7
+
8
+ def validate_context!
9
+ validable_properties.each do |property|
10
+ result = context.send property.to_sym
11
+ context_fail! "Invalid context for #{property} [#{result}]" unless result
12
+ end
13
+ end
14
+
15
+ def context_fail!(msg)
16
+ context.fail! error: Pulsar::ContextError.new(msg)
17
+ end
18
+
19
+ def validable_properties
20
+ self.class.validable_properties
21
+ end
22
+
23
+ module ClassMethods
24
+ def validate_context_for!(*args)
25
+ validable_properties.concat args
26
+ end
27
+
28
+ def validable_properties
29
+ @validable_properties ||= []
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Pulsar
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'pulsar/version'
@@ -29,7 +28,8 @@ Gem::Specification.new do |gem|
29
28
 
30
29
  gem.add_development_dependency 'rake', '~> 10.4'
31
30
  gem.add_development_dependency 'rspec', '~> 3.2'
32
- gem.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
33
31
  gem.add_development_dependency 'rubocop', '~> 0.47'
34
32
  gem.add_development_dependency 'timecop', '~> 0.8'
33
+ gem.add_development_dependency 'simplecov', '~> 0.14.0'
34
+ gem.add_development_dependency 'coveralls', '~> 0.8.20'
35
35
  end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Task' do
4
+ subject { -> { command } }
5
+
6
+ let(:command) do
7
+ `DRY_RUN=true #{RSpec.configuration.pulsar_command} task #{options} #{arguments} #{task}`
8
+ end
9
+
10
+ let(:repo) { RSpec.configuration.pulsar_conf_path }
11
+ let(:task) { 'deploy:check' }
12
+ let(:options) { "--conf-repo #{repo}" }
13
+ let(:app) { 'blog' }
14
+ let(:environment) { 'production' }
15
+ let(:arguments) { "#{app} #{environment}" }
16
+
17
+ context 'via a subcommand named task' do
18
+ let(:error) { /Could not find command/ }
19
+
20
+ it { is_expected.not_to output(error).to_stderr_from_any_process }
21
+ end
22
+
23
+ context 'requires a --conf-repo option' do
24
+ let(:options) { nil }
25
+ let(:error) { /No value provided for required options '--conf-repo'/ }
26
+
27
+ it { is_expected.to output(error).to_stderr_from_any_process }
28
+
29
+ context 'can be specified via the alias -c' do
30
+ let(:options) { "-c #{repo}" }
31
+
32
+ it { is_expected.not_to output(error).to_stderr_from_any_process }
33
+ end
34
+
35
+ context 'can be specified via the environment variable PULSAR_CONF_REPO' do
36
+ before { ENV['PULSAR_CONF_REPO'] = repo }
37
+
38
+ it { is_expected.not_to output(error).to_stderr_from_any_process }
39
+ end
40
+ end
41
+
42
+ context 'requires application and environment arguments' do
43
+ let(:app) { nil }
44
+ let(:environment) { nil }
45
+ let(:error) { /Usage: "pulsar task APPLICATION ENVIRONMENT TASK"/ }
46
+
47
+ it { is_expected.to output(error).to_stderr_from_any_process }
48
+ end
49
+
50
+ context 'when succeeds' do
51
+ subject { command }
52
+
53
+ context 'deploys an application on a environment in the pulsar configuration' do
54
+ let(:output) { /Executed task deploy:check for blog on production!/ }
55
+
56
+ context 'from a local folder' do
57
+ let(:repo) { Dir.mktmpdir }
58
+
59
+ before do
60
+ FileUtils.cp_r("#{RSpec.configuration.pulsar_conf_path}/.", repo)
61
+ end
62
+
63
+ context 'with Capistrano parameters passed via command line' do
64
+ let(:task) { "deploy:check[param1,param2]" }
65
+ let(:output) { /Executed task deploy:check\[param1,param2\] for blog on production!\n/ }
66
+
67
+ it { is_expected.to match(output) }
68
+ end
69
+
70
+ context 'without Capistrano parameters' do
71
+ it { is_expected.to match(output) }
72
+ end
73
+
74
+ context 'leaves the tmp folder empty' do
75
+ subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") }
76
+
77
+ before { command }
78
+
79
+ it { is_expected.to be_empty }
80
+ end
81
+ end
82
+
83
+ context 'from a remote Git repository' do
84
+ let(:repo) { RSpec.configuration.pulsar_remote_git_conf }
85
+ let(:app) { 'your_app' }
86
+ let(:environment) { 'staging' }
87
+ let(:output) { /Executed task deploy:check for your_app on staging!\n/ }
88
+
89
+ context 'with Capistrano parameters passed via command line' do
90
+ let(:task) { "deploy:check[param1,param2]" }
91
+ let(:output) { /Executed task deploy:check\[param1,param2\] for your_app on staging!\n/ }
92
+
93
+ it { is_expected.to match(output) }
94
+ end
95
+
96
+ context 'without Capistrano parameters' do
97
+ it { is_expected.to match(output) }
98
+ end
99
+
100
+ context 'leaves the tmp folder empty' do
101
+ subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") }
102
+
103
+ before { command }
104
+
105
+ it { is_expected.to be_empty }
106
+ end
107
+ end
108
+
109
+ context 'from a remote GitHub repository' do
110
+ let(:repo) { RSpec.configuration.pulsar_remote_github_conf }
111
+ let(:app) { 'your_app' }
112
+ let(:environment) { 'staging' }
113
+ let(:output) { /Executed task deploy:check for your_app on staging!\n/ }
114
+
115
+ context 'with Capistrano parameters passed via command line' do
116
+ let(:task) { "deploy:check[param1,param2]" }
117
+ let(:output) { /Executed task deploy:check\[param1,param2\] for your_app on staging!\n/ }
118
+
119
+ it { is_expected.to match(output) }
120
+ end
121
+
122
+ context 'without Capistrano parameters' do
123
+ it { is_expected.to match(output) }
124
+ end
125
+
126
+ context 'leaves the tmp folder empty' do
127
+ subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") }
128
+
129
+ before { command }
130
+
131
+ it { is_expected.to be_empty }
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ context 'when fails' do
138
+ subject { command }
139
+
140
+ context 'because of wrong directory' do
141
+ let(:repo) { './some-wrong-directory' }
142
+
143
+ it { is_expected.to match(/Failed to execute task deploy:check for blog on production.\n/) }
144
+ end
145
+
146
+ context 'because of empty directory' do
147
+ let(:repo) { RSpec.configuration.pulsar_empty_conf_path }
148
+
149
+ it { is_expected.to match(/Failed to execute task deploy:check for blog on production.\n/) }
150
+ it { is_expected.to match "No application found on repository #{RSpec.configuration.pulsar_empty_conf_path}\n" }
151
+ end
152
+
153
+ context 'because Bundler failed' do
154
+ let(:repo) { RSpec.configuration.pulsar_wrong_bundle_conf_path }
155
+
156
+ it { is_expected.to match(/Failed to execute task deploy:check for blog on production.\n/) }
157
+ end
158
+
159
+ context 'because Capistrano failed' do
160
+ let(:repo) { RSpec.configuration.pulsar_wrong_cap_conf_path }
161
+
162
+ it { is_expected.to match(/Failed to execute task deploy:check for blog on production.\n/) }
163
+ end
164
+
165
+ context 'because the application does not exists in the repository' do
166
+ let(:repo) { RSpec.configuration.pulsar_conf_path }
167
+ let(:app) { 'foobuzz' }
168
+ let(:environment) { 'staging' }
169
+
170
+ it { is_expected.to match(/The application foobuzz does not exist in your repository/) }
171
+ end
172
+
173
+ context 'because the environment does not exists for the application' do
174
+ let(:repo) { RSpec.configuration.pulsar_conf_path }
175
+ let(:app) { 'blog' }
176
+ let(:environment) { 'foobuzz' }
177
+
178
+ it { is_expected.to match(/The application blog does not have an environment called foobuzz/) }
179
+
180
+ context 'but \'no application error\' message takes precedence' do
181
+ let(:app) { 'foobuzz' }
182
+
183
+ it { is_expected.to match(/The application foobuzz does not exist in your repository/) }
184
+ end
185
+ end
186
+ end
187
+ end