pulsar 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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