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
@@ -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 }