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
@@ -1,16 +1,11 @@
1
+ require 'support/coverage_setup'
1
2
  require 'rspec'
2
3
  require 'stringio'
3
4
  require 'fileutils'
4
5
  require 'timecop'
5
6
  require 'tmpdir'
6
- require 'codeclimate-test-reporter'
7
7
  require 'pulsar'
8
8
 
9
- #
10
- # Code coverage
11
- #
12
- CodeClimate::TestReporter.start
13
-
14
9
  RSpec.configure do |config|
15
10
  config.mock_with :rspec
16
11
  config.raise_errors_for_deprecations!
@@ -0,0 +1,39 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatters = [
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+
9
+ # Save to CircleCI's artifacts directory if we're on CircleCI
10
+ if ENV['CIRCLE_ARTIFACTS']
11
+ dir = File.join(ENV['CIRCLE_ARTIFACTS'], "coverage")
12
+ SimpleCov.coverage_dir(dir)
13
+ end
14
+
15
+ SimpleCov.minimum_coverage 99
16
+ SimpleCov.minimum_coverage_by_file 90
17
+ SimpleCov.refuse_coverage_drop
18
+
19
+ if ENV['FEATURE_TESTS']
20
+ SimpleCov.command_name 'features'
21
+
22
+ # This is needed because otherwise SimpleCov will output some text at exit
23
+ # and it will make most of the feature specs fail (that check the output).
24
+ SimpleCov.at_exit do
25
+ $stdout.reopen(File::NULL, 'w')
26
+ $stdout.sync = true
27
+ SimpleCov.result.format!
28
+ $stdout = STDOUT
29
+ end
30
+ end
31
+
32
+ if ENV['COVERAGE']
33
+ SimpleCov.start do
34
+ add_group 'Interactors', 'lib/pulsar/interactors'
35
+ add_group 'Organizers', 'lib/pulsar/organizers'
36
+
37
+ add_filter 'spec/*'
38
+ end
39
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Pulsar::CLI do
4
- subject { Pulsar::Deploy }
4
+ subject { Pulsar::Task }
5
5
 
6
6
  let(:described_instance) { described_class.new }
7
7
  let(:fail_text) { /Failed to deploy blog on production./ }
@@ -12,7 +12,7 @@ RSpec.describe Pulsar::CLI do
12
12
 
13
13
  before do
14
14
  allow($stdout).to receive(:puts)
15
- allow(Pulsar::Deploy).to receive(:call).and_return(result)
15
+ allow(Pulsar::Task).to receive(:call).and_return(result)
16
16
  end
17
17
 
18
18
  context 'when using --conf-repo' do
@@ -24,7 +24,7 @@ RSpec.describe Pulsar::CLI do
24
24
 
25
25
  specify do
26
26
  is_expected.to have_received(:call)
27
- .with(repository: repo, application: 'blog', environment: 'production')
27
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy')
28
28
  end
29
29
 
30
30
  context 'success' do
@@ -46,8 +46,10 @@ RSpec.describe Pulsar::CLI do
46
46
  end
47
47
 
48
48
  context 'when using configuration file' do
49
+ let(:options) { {} }
50
+
49
51
  before do
50
- allow(described_instance).to receive(:options).and_return({})
52
+ allow(described_instance).to receive(:options).and_return(options)
51
53
  described_instance.deploy('blog', 'production')
52
54
  end
53
55
 
@@ -62,7 +64,7 @@ RSpec.describe Pulsar::CLI do
62
64
 
63
65
  specify do
64
66
  is_expected.to have_received(:call)
65
- .with(repository: repo, application: 'blog', environment: 'production')
67
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy')
66
68
  end
67
69
 
68
70
  context 'success' do
@@ -72,6 +74,18 @@ RSpec.describe Pulsar::CLI do
72
74
  let(:result) { spy(success?: true) }
73
75
 
74
76
  it { is_expected.to output(/#{success}/).to_stdout }
77
+
78
+ context 'when file is unaccessible' do
79
+ let(:options) { { conf_repo: repo } }
80
+
81
+ around do |example|
82
+ system("chmod 000 #{RSpec.configuration.pulsar_dotenv_conf_path}")
83
+ example.run
84
+ system("chmod 644 #{RSpec.configuration.pulsar_dotenv_conf_path}")
85
+ end
86
+
87
+ it { is_expected.to output(/#{success}/).to_stdout }
88
+ end
75
89
  end
76
90
 
77
91
  context 'failure' do
@@ -92,7 +106,7 @@ RSpec.describe Pulsar::CLI do
92
106
 
93
107
  specify do
94
108
  is_expected.to have_received(:call)
95
- .with(repository: repo, application: 'blog', environment: 'production')
109
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy')
96
110
  end
97
111
 
98
112
  context 'success' do
@@ -112,5 +126,13 @@ RSpec.describe Pulsar::CLI do
112
126
  it { is_expected.to output(fail_text).to_stdout }
113
127
  end
114
128
  end
129
+
130
+ context 'when no configuration repo is passed' do
131
+ context 'failure' do
132
+ subject { -> { described_instance.deploy('blog', 'production') } }
133
+
134
+ it { is_expected.to raise_error(Thor::RequiredArgumentMissingError) }
135
+ end
136
+ end
115
137
  end
116
138
  end
@@ -40,5 +40,27 @@ RSpec.describe Pulsar::CLI do
40
40
 
41
41
  it { is_expected.to output(/Failed to create intial repo./).to_stdout }
42
42
  end
43
+
44
+ context 'when an error is reported' do
45
+ subject { -> { described_instance.install } }
46
+ before do
47
+ allow(described_instance).to receive(:options).and_return(conf_repo: repo)
48
+ described_instance.list
49
+ end
50
+
51
+ let(:repo) { RSpec.configuration.pulsar_conf_path }
52
+
53
+ context 'as a string' do
54
+ let(:result) { spy(success?: false, error: "A stub sets this error") }
55
+
56
+ it { is_expected.to output(/A stub sets this error/).to_stdout }
57
+ end
58
+
59
+ context 'as an exception object' do
60
+ let(:result) { spy(success?: false, error: RuntimeError.new("A stub sets this error")) }
61
+
62
+ it { is_expected.to output(/A stub sets this error/).to_stdout }
63
+ end
64
+ end
43
65
  end
44
66
  end
@@ -43,8 +43,10 @@ RSpec.describe Pulsar::CLI do
43
43
  end
44
44
 
45
45
  context 'when using configuration file' do
46
+ let(:options) { {} }
47
+
46
48
  before do
47
- allow(described_instance).to receive(:options).and_return({})
49
+ allow(described_instance).to receive(:options).and_return(options)
48
50
  described_instance.list
49
51
  end
50
52
 
@@ -66,6 +68,18 @@ RSpec.describe Pulsar::CLI do
66
68
  let(:result) { spy(success?: true, applications: applications) }
67
69
 
68
70
  it { is_expected.to output(/blog: staging/).to_stdout }
71
+
72
+ context 'when file is unaccessible' do
73
+ let(:options) { { conf_repo: repo } }
74
+
75
+ around do |example|
76
+ system("chmod 000 #{RSpec.configuration.pulsar_dotenv_conf_path}")
77
+ example.run
78
+ system("chmod 644 #{RSpec.configuration.pulsar_dotenv_conf_path}")
79
+ end
80
+
81
+ it { is_expected.to output(/blog: staging/).to_stdout }
82
+ end
69
83
  end
70
84
 
71
85
  context 'failure' do
@@ -103,5 +117,13 @@ RSpec.describe Pulsar::CLI do
103
117
  it { is_expected.to output(fail_text).to_stdout }
104
118
  end
105
119
  end
120
+
121
+ context 'when no configuration repo is passed' do
122
+ context 'failure' do
123
+ subject { -> { described_instance.deploy('blog', 'production') } }
124
+
125
+ it { is_expected.to raise_error(Thor::RequiredArgumentMissingError) }
126
+ end
127
+ end
106
128
  end
107
129
  end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Pulsar::CLI do
4
+ subject { Pulsar::Task }
5
+
6
+ let(:described_instance) { described_class.new }
7
+ let(:fail_text) { /Failed to execute task deploy:check for blog on production./ }
8
+
9
+ context '#task' do
10
+ let(:result) { spy }
11
+ let(:repo) { './conf_repo' }
12
+
13
+ before do
14
+ allow($stdout).to receive(:puts)
15
+ allow(Pulsar::Task).to receive(:call).and_return(result)
16
+ end
17
+
18
+ context 'when using --conf-repo' do
19
+ before do
20
+ allow(described_instance)
21
+ .to receive(:options).and_return(conf_repo: repo)
22
+ described_instance.task('blog', 'production', 'deploy:check')
23
+ end
24
+
25
+ specify do
26
+ is_expected.to have_received(:call)
27
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy:check')
28
+ end
29
+
30
+ context 'success' do
31
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
32
+
33
+ let(:success) { "Executed task deploy:check for blog on production!" }
34
+ let(:result) { spy(success?: true) }
35
+
36
+ it { is_expected.to output(/#{success}/).to_stdout }
37
+ end
38
+
39
+ context 'failure' do
40
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
41
+
42
+ let(:result) { spy(success?: false) }
43
+
44
+ it { is_expected.to output(fail_text).to_stdout }
45
+ end
46
+ end
47
+
48
+ context 'when using configuration file' do
49
+ let(:options) { {} }
50
+
51
+ before do
52
+ allow(described_instance).to receive(:options).and_return(options)
53
+ described_instance.task('blog', 'production', 'deploy:check')
54
+ end
55
+
56
+ around do |example|
57
+ old_const = Pulsar::PULSAR_CONF
58
+ Pulsar.send(:remove_const, 'PULSAR_CONF')
59
+ Pulsar::PULSAR_CONF = RSpec.configuration.pulsar_dotenv_conf_path
60
+ example.run
61
+ Pulsar.send(:remove_const, 'PULSAR_CONF')
62
+ Pulsar::PULSAR_CONF = old_const
63
+ end
64
+
65
+ specify do
66
+ is_expected.to have_received(:call)
67
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy:check')
68
+ end
69
+
70
+ context 'success' do
71
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
72
+
73
+ let(:success) { "Executed task deploy:check for blog on production!" }
74
+ let(:result) { spy(success?: true) }
75
+
76
+ it { is_expected.to output(/#{success}/).to_stdout }
77
+
78
+ context 'when file is unaccessible' do
79
+ let(:options) { { conf_repo: repo } }
80
+
81
+ around do |example|
82
+ system("chmod 000 #{RSpec.configuration.pulsar_dotenv_conf_path}")
83
+ example.run
84
+ system("chmod 644 #{RSpec.configuration.pulsar_dotenv_conf_path}")
85
+ end
86
+
87
+ it { is_expected.to output(/#{success}/).to_stdout }
88
+ end
89
+ end
90
+
91
+ context 'failure' do
92
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
93
+
94
+ let(:result) { spy(success?: false) }
95
+
96
+ it { is_expected.to output(fail_text).to_stdout }
97
+ end
98
+ end
99
+
100
+ context 'when using PULSAR_CONF_REPO' do
101
+ before do
102
+ allow(described_instance).to receive(:options).and_return({})
103
+ ENV['PULSAR_CONF_REPO'] = repo
104
+ described_instance.task('blog', 'production', 'deploy:check')
105
+ end
106
+
107
+ specify do
108
+ is_expected.to have_received(:call)
109
+ .with(repository: repo, application: 'blog', environment: 'production', task: 'deploy:check')
110
+ end
111
+
112
+ context 'success' do
113
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
114
+
115
+ let(:success) { "Executed task deploy:check for blog on production!" }
116
+ let(:result) { spy(success?: true) }
117
+
118
+ it { is_expected.to output(/#{success}/).to_stdout }
119
+ end
120
+
121
+ context 'failure' do
122
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
123
+
124
+ let(:result) { spy(success?: false) }
125
+
126
+ it { is_expected.to output(fail_text).to_stdout }
127
+ end
128
+ end
129
+
130
+ context 'when no configuration repo is passed' do
131
+ context 'failure' do
132
+ subject { -> { described_instance.task('blog', 'production', 'deploy:check') } }
133
+
134
+ it { is_expected.to raise_error(Thor::RequiredArgumentMissingError) }
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Pulsar::CLI do
4
+ subject { Pulsar::Version }
5
+
6
+ let(:described_instance) { described_class.new }
7
+
8
+ context '#__print_version' do
9
+ subject { -> { described_instance.__print_version } }
10
+
11
+ it { is_expected.to output(/#{Pulsar::VERSION}/).to_stdout }
12
+ end
13
+ end
@@ -78,6 +78,31 @@ RSpec.describe Pulsar::CopyEnvironmentFile do
78
78
 
79
79
  it { is_expected.to be_a_failure }
80
80
  end
81
+
82
+ context 'when passing a missing environment' do
83
+ let(:args) do
84
+ {
85
+ config_path: RSpec.configuration.pulsar_conf_path,
86
+ cap_path: cap_path,
87
+ applications: { 'blog' => %w(production development) },
88
+ application: 'blog',
89
+ environment: 'staging'
90
+ }
91
+ end
92
+
93
+ it { is_expected.to be_a_failure }
94
+
95
+ context 'shows a proper error message' do
96
+ subject { command.error }
97
+
98
+ let(:error) do
99
+ 'The application blog does not have an environment called staging'
100
+ end
101
+
102
+ it { is_expected.to be_an_instance_of Pulsar::ContextError }
103
+ it { expect(subject.message).to eql error }
104
+ end
105
+ end
81
106
  end
82
107
  end
83
108
  end
@@ -72,6 +72,28 @@ RSpec.describe Pulsar::CreateCapfile do
72
72
 
73
73
  it { is_expected.to be_a_failure }
74
74
  end
75
+
76
+ context 'when passing a missing application' do
77
+ let(:args) do
78
+ {
79
+ config_path: RSpec.configuration.pulsar_conf_path,
80
+ cap_path: cap_path, application: 'wiki', applications: { 'blog' => %w(staging) }
81
+ }
82
+ end
83
+
84
+ it { is_expected.to be_a_failure }
85
+
86
+ context 'shows a proper error message' do
87
+ subject { command.error }
88
+
89
+ let(:error) do
90
+ 'The application wiki does not exist in your repository'
91
+ end
92
+
93
+ it { is_expected.to be_an_instance_of Pulsar::ContextError }
94
+ it { expect(subject.message).to eql(error) }
95
+ end
96
+ end
75
97
  end
76
98
  end
77
99
  end
@@ -6,24 +6,51 @@ RSpec.describe Pulsar::RunCapistrano do
6
6
  it { is_expected.to be_kind_of(Interactor) }
7
7
 
8
8
  describe '.success' do
9
- subject do
10
- described_class.new(
9
+ subject { described_class.new context_params }
10
+ let(:context_params) do
11
+ {
11
12
  cap_path: RSpec.configuration.pulsar_conf_path, config_path: './config-path',
12
- bundle_path: './bundle-path', environment: 'production'
13
- )
13
+ bundle_path: './bundle-path', environment: 'production',
14
+ task: task
15
+ }
14
16
  end
15
-
16
- let(:cap_cmd) { "cap production deploy" }
17
17
  let(:bundle_cmd) do
18
18
  "BUNDLE_GEMFILE=./config-path/Gemfile BUNDLE_PATH=./bundle-path bundle exec"
19
19
  end
20
20
 
21
- before do
22
- allow(Rake).to receive(:sh).and_return(true)
23
- subject.run
21
+ context 'for plain old deploy command' do
22
+ let(:cap_cmd) { "cap production deploy" }
23
+ let(:task) { 'deploy' }
24
+ before do
25
+ allow(Rake).to receive(:sh).and_return(true)
26
+ subject.run
27
+ end
28
+
29
+ it { expect(Rake).to have_received(:sh).with("#{bundle_cmd} #{cap_cmd}") }
24
30
  end
25
31
 
26
- it { expect(Rake).to have_received(:sh).with("#{bundle_cmd} #{cap_cmd}") }
32
+ context 'for other capistrano tasks' do
33
+ context 'without Capistrano arguments' do
34
+ let(:cap_cmd) { "cap production deploy:check" }
35
+ let(:task) { 'deploy:check' }
36
+ before do
37
+ allow(Rake).to receive(:sh).and_return(true)
38
+ subject.run
39
+ end
40
+
41
+ it { expect(Rake).to have_received(:sh).with("#{bundle_cmd} #{cap_cmd}") }
42
+ end
43
+ context 'with Capistrano arguments' do
44
+ let(:cap_cmd) { "cap production deploy:check[param1,param2]" }
45
+ let(:task) { 'deploy:check[param1,param2]' }
46
+ before do
47
+ allow(Rake).to receive(:sh).and_return(true)
48
+ subject.run
49
+ end
50
+
51
+ it { expect(Rake).to have_received(:sh).with("#{bundle_cmd} #{cap_cmd}") }
52
+ end
53
+ end
27
54
  end
28
55
 
29
56
  context 'failure' do
@@ -37,7 +64,7 @@ RSpec.describe Pulsar::RunCapistrano do
37
64
  subject do
38
65
  described_class.call(
39
66
  config_path: './config-path', bundle_path: './bundle-path',
40
- environment: 'production'
67
+ environment: 'production', task: 'deploy:check'
41
68
  )
42
69
  end
43
70
 
@@ -48,17 +75,18 @@ RSpec.describe Pulsar::RunCapistrano do
48
75
  subject do
49
76
  described_class.call(
50
77
  cap_path: './cap-path', config_path: './config-path',
51
- bundle_path: './bundle-path'
78
+ bundle_path: './bundle-path', task: 'deploy:check'
52
79
  )
53
80
  end
54
81
 
55
82
  it { is_expected.to be_a_failure }
56
83
  end
84
+
57
85
  context 'when no bundle_path context is passed' do
58
86
  subject do
59
87
  described_class.call(
60
88
  cap_path: './cap-path', config_path: './config-path',
61
- environment: 'production'
89
+ environment: 'production', task: 'deploy:check'
62
90
  )
63
91
  end
64
92
 
@@ -69,14 +97,14 @@ RSpec.describe Pulsar::RunCapistrano do
69
97
  subject do
70
98
  described_class.call(
71
99
  cap_path: './cap-path', environment: 'production',
72
- bundle_path: './bundle-path'
100
+ bundle_path: './bundle-path', task: 'deploy:check'
73
101
  )
74
102
  end
75
103
 
76
104
  it { is_expected.to be_a_failure }
77
105
  end
78
106
 
79
- context 'when an exception is triggered' do
107
+ context 'when no task is passed' do
80
108
  subject do
81
109
  described_class.call(
82
110
  cap_path: './cap-path', config_path: './config-path',
@@ -84,6 +112,38 @@ RSpec.describe Pulsar::RunCapistrano do
84
112
  )
85
113
  end
86
114
 
115
+ it { is_expected.to be_a_failure }
116
+ end
117
+
118
+ context 'when the task does not exist' do
119
+ subject do
120
+ described_class.call(
121
+ cap_path: './cap-path', config_path: './config-path',
122
+ bundle_path: './bundle-path', environment: 'production',
123
+ task: 'unexistent:task'
124
+ )
125
+ end
126
+ it { is_expected.to be_a_failure }
127
+ end
128
+
129
+ context 'when the task is malformed' do
130
+ subject do
131
+ described_class.call(
132
+ cap_path: './cap-path', config_path: './config-path',
133
+ bundle_path: './bundle-path', environment: 'production',
134
+ task: 'unexistent:task[param1,pa'
135
+ )
136
+ end
137
+ it { is_expected.to be_a_failure }
138
+ end
139
+ context 'when an exception is triggered' do
140
+ subject do
141
+ described_class.call(
142
+ cap_path: './cap-path', config_path: './config-path',
143
+ bundle_path: './bundle-path', environment: 'production',
144
+ task: 'deploy:check'
145
+ )
146
+ end
87
147
  before { allow(Dir).to receive(:chdir).and_raise(RuntimeError) }
88
148
 
89
149
  it { is_expected.to be_a_failure }