guard-bosh 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ require 'guard/bosh/job_default_properties_loader'
2
+
3
+ require 'pathname'
4
+ require 'yaml'
5
+
6
+ describe Guard::Bosh::JobDefaultPropertiesLoader do
7
+ let(:release_dir) { Pathname.new('/path/to/a/release') }
8
+ let(:job_spec_excerpt) do
9
+ {
10
+ 'properties' =>
11
+ {
12
+ 'redis.port' => {
13
+ 'description' => 'Port to listen for requests to redis server',
14
+ 'default' => 6379
15
+ },
16
+ 'redis.password' => {
17
+ 'description' => 'Password to access redis server',
18
+ 'default' => 'password'
19
+ },
20
+ 'redis.master' => {
21
+ 'description' => 'IP address or hostname of the Redis master node'
22
+ },
23
+ 'redis.compression.rdb' => {
24
+ 'description' => 'Compress when dumping databases?',
25
+ 'default' => true
26
+ },
27
+ 'redis.blazingly.fast' => {
28
+ 'description' => 'Empty hash should be created for parent key'
29
+ }
30
+ }
31
+ }
32
+ end
33
+ subject do
34
+ Guard::Bosh::JobDefaultPropertiesLoader.new(
35
+ release_dir: release_dir
36
+ )
37
+ end
38
+
39
+ it 'returns the properties that have defaults in the job spec' do
40
+ expect(YAML).to receive(:load_file).with(
41
+ Pathname.new('/path/to/a/release/jobs/job-name/spec')).and_return(job_spec_excerpt)
42
+ job_defaults = subject.load_properties(job_name: 'job-name')
43
+ expect(job_defaults).to include(
44
+ 'redis' => {
45
+ 'blazingly' => {},
46
+ 'port' => 6379,
47
+ 'password' => 'password',
48
+ 'compression' => {
49
+ 'rdb' => true
50
+ }
51
+ }
52
+ )
53
+ end
54
+
55
+ it 'ignores properties that do not have defaults defined' do
56
+ expect(YAML).to receive(:load_file).with(
57
+ Pathname.new('/path/to/a/release/jobs/job-name/spec')).and_return(job_spec_excerpt)
58
+ job_defaults = subject.load_properties(job_name: 'job-name')
59
+ expect(job_defaults['redis'].keys).not_to include('master')
60
+ end
61
+
62
+ it 'includes an empty hash for intermediate keys' do
63
+ expect(YAML).to receive(:load_file).with(
64
+ Pathname.new('/path/to/a/release/jobs/job-name/spec')).and_return(job_spec_excerpt)
65
+ job_defaults = subject.load_properties(job_name: 'job-name')
66
+ expect(job_defaults['redis'].keys).to include('blazingly')
67
+ expect(job_defaults['redis']['blazingly']).to be_empty
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ require 'guard/bosh/job_properties_loader'
2
+
3
+ describe Guard::Bosh::JobPropertiesLoader do
4
+ let(:deployment_manifest) do
5
+ {
6
+ 'jobs' => [
7
+ {
8
+ 'instances' => 1,
9
+ 'name' => 'redis_leader_z1',
10
+ 'networks' => [{ 'name' => 'redis1', 'static_ips' => ['10.244.2.6'] }],
11
+ 'persistent_disk' => 0,
12
+ 'properties' => { 'network' => 'redis1', 'redis' => nil },
13
+ 'resource_pool' => 'small_z1',
14
+ 'templates' => [{ 'name' => 'redis', 'release' => 'redis' }]
15
+ },
16
+ {
17
+ 'instances' => 2,
18
+ 'name' => 'redis_z1',
19
+ 'networks' => [{ 'name' => 'redis1', 'static_ips' => ['10.244.2.10', '10.244.2.14'] }],
20
+ 'persistent_disk' => 0,
21
+ 'properties' => { 'network' => 'redis1', 'redis' => { 'master' => '10.244.2.6' } },
22
+ 'resource_pool' => 'small_z1',
23
+ 'templates' => [{ 'name' => 'redis', 'release' => 'redis' }],
24
+ 'update' => { 'canaries' => 10 }
25
+ }
26
+ ]
27
+ }
28
+ end
29
+ subject do
30
+ Guard::Bosh::JobPropertiesLoader.new(
31
+ deployment_manifest: deployment_manifest
32
+ )
33
+ end
34
+
35
+ context 'when the job exists within the deployment manifest' do
36
+ it 'returns the properties defined at the job level' do
37
+ job_properties = subject.load_properties(manifest_job_name: 'redis_z1')
38
+ expect(job_properties).to eq(
39
+ 'network' => 'redis1', 'redis' => { 'master' => '10.244.2.6' }
40
+ )
41
+ end
42
+ end
43
+
44
+ context 'when the job does not exist in the deployment manifest' do
45
+ it 'returns an empty' do
46
+ job_properties = subject.load_properties(manifest_job_name: 'missing_job')
47
+ expect(job_properties).to be_empty
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ require 'guard/bosh/job_repository'
2
+
3
+ describe Guard::Bosh::JobRepository do
4
+ let(:manifest) do
5
+ {
6
+ 'jobs' => [
7
+ {
8
+ 'name' => 'redis_leader_z1',
9
+ 'templates' => [{ 'name' => 'redis', 'release' => 'redis' }]
10
+ },
11
+ {
12
+ 'name' => 'postgresql_z1',
13
+ 'template' => 'postgresql',
14
+ 'properties' => {}
15
+ },
16
+ {
17
+ 'name' => 'redis_slave_z2',
18
+ 'templates' => [{ 'name' => 'redis', 'release' => 'redis' }]
19
+ }
20
+ ]
21
+ }
22
+ end
23
+
24
+ subject do
25
+ Guard::Bosh::JobRepository.new(manifest)
26
+ end
27
+
28
+ describe '#job_templates' do
29
+ it 'returns all job templates' do
30
+ expect(subject.job_templates).to eq(%w(postgresql redis))
31
+ end
32
+ end
33
+
34
+ describe '#find_by_template' do
35
+ context 'when there are multiple jobs that use a job template' do
36
+ it 'returns all jobs that use that template' do
37
+ expect(subject.find_by_template('redis')).to eq(%w(redis_leader_z1 redis_slave_z2))
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#template_paths' do
43
+ it 'looks up the template paths from the job specification' do
44
+ expect(YAML).to receive(:load_file).with(Pathname.new('jobs/redis/spec')).and_return(
45
+ 'templates' => {
46
+ 'redis_ctl.sh.erb' => 'bin/redis_ctl.sh',
47
+ 'redis.conf.erb' => 'config/redis.conf'
48
+ }
49
+ )
50
+ expect(subject.template_paths('redis')).to eq([
51
+ Pathname.new('jobs/redis/templates/redis_ctl.sh.erb'),
52
+ Pathname.new('jobs/redis/templates/redis.conf.erb')
53
+ ])
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ require 'guard/bosh/notifier'
2
+
3
+ describe Guard::Bosh::Notifier do
4
+ before do
5
+ allow(Guard::Compat::UI).to receive(:notify)
6
+ allow(Guard::Compat::UI).to receive(:error)
7
+ end
8
+
9
+ context 'when there are no errors' do
10
+ it 'reports success' do
11
+ expect(Guard::Compat::UI).to receive(:notify).with(
12
+ 'Succeeded',
13
+ title: 'BOSH',
14
+ image: :success,
15
+ priority: -2
16
+ )
17
+ subject.notify([])
18
+ end
19
+ end
20
+
21
+ context 'when there are errors' do
22
+ it 'reports failure' do
23
+ expect(Guard::Compat::UI).to receive(:notify).with(
24
+ 'Failed',
25
+ title: 'BOSH',
26
+ image: :failed,
27
+ priority: 2
28
+ )
29
+ subject.notify([
30
+ { template: 'config.erb', status: :failure, detail: 'Missing property: redis.port' }
31
+ ])
32
+ end
33
+ it 'outputs the template the error occurred in and the detail' do
34
+ expect(Guard::Compat::UI).to receive(:error).with('config.erb: Missing property: redis.port')
35
+ subject.notify([
36
+ { template: 'config.erb', status: :failure, detail: 'Missing property: redis.port' }
37
+ ])
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ require 'guard/bosh/package_resolver'
2
+
3
+ require 'pathname'
4
+
5
+ describe Guard::Bosh::PackageResolver do
6
+ subject do
7
+ Guard::Bosh::PackageResolver.new(Pathname.new('/path/to/release/dir/'))
8
+ end
9
+
10
+ it 'resolves the package associated with the specified job' do
11
+ expect(YAML).to receive(:load_file).with(
12
+ Pathname.new('/path/to/release/dir/jobs/redis/spec')
13
+ ).and_return('packages' => ['redis'])
14
+ expect(subject.resolve('redis')).to eq(['redis'])
15
+ end
16
+
17
+ it 'raises if the job spec cannot be loaded' do
18
+ expect(YAML).to receive(:load_file)
19
+ expect do
20
+ subject.resolve('redis')
21
+ end.to raise_error
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ require 'guard/bosh/template_checker'
2
+
3
+ require 'guard/bosh/effective_properties_calculator'
4
+
5
+ describe Guard::Bosh::TemplateChecker do
6
+ let(:properties_calculator) do
7
+ instance_double(Guard::Bosh::EffectivePropertiesCalculator)
8
+ end
9
+ let(:apply_specification) do
10
+ instance_double(Guard::Bosh::ApplySpecification)
11
+ end
12
+ let(:template_renderer) do
13
+ instance_double(Guard::Bosh::TemplateRenderer)
14
+ end
15
+ subject do
16
+ Guard::Bosh::TemplateChecker.new(
17
+ deployment_manifest: 'a/deployment/manifest.yml',
18
+ properties_calculator: properties_calculator,
19
+ apply_specification: apply_specification,
20
+ template_renderer: template_renderer
21
+ )
22
+ end
23
+
24
+ before do
25
+ allow(properties_calculator).to receive(:calculate_effective_properties)
26
+ allow(apply_specification).to receive(:generate).and_return('apply' => 'specification')
27
+ allow(template_renderer).to receive(:render)
28
+ end
29
+
30
+ it 'builds a context to evaluate the template in' do
31
+ expect(properties_calculator).to receive(
32
+ :calculate_effective_properties).with(
33
+ manifest_job_name: 'redis_leader_z1',
34
+ job_name: 'redis'
35
+ ).and_return('effective' => 'properties')
36
+ expect(apply_specification).to receive(:generate).with(
37
+ properties: { 'effective' => 'properties' },
38
+ job_name: 'redis_leader_z1')
39
+ subject.check(
40
+ manifest_job_name: 'redis_leader_z1',
41
+ job_name: 'redis',
42
+ template: 'jobs/redis/templates/redis.conf.erb'
43
+ )
44
+ end
45
+
46
+ it 'renders the template in context' do
47
+ expect(template_renderer).to receive(:render).with(
48
+ context: { 'apply' => 'specification' },
49
+ template: 'jobs/redis/templates/redis.conf.erb'
50
+ )
51
+ subject.check(
52
+ manifest_job_name: 'redis_leader_z1',
53
+ job_name: 'redis',
54
+ template: 'jobs/redis/templates/redis.conf.erb'
55
+ )
56
+ end
57
+
58
+ it 'returns any errors generated by the template render' do
59
+ expect(template_renderer).to receive(:render).and_return(['missing property'])
60
+ errors = subject.check(
61
+ manifest_job_name: 'redis_leader_z1',
62
+ job_name: 'redis',
63
+ template: 'jobs/redis/templates/redis.conf.erb'
64
+ )
65
+ expect(errors).to eq(['missing property'])
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ require 'guard/bosh/template_renderer'
2
+
3
+ require 'pathname'
4
+
5
+ describe Guard::Bosh::TemplateRenderer do
6
+ subject do
7
+ Guard::Bosh::TemplateRenderer.new
8
+ end
9
+
10
+ context 'when the template contains no errors' do
11
+ it 'reports that no error occurred' do
12
+ bosh_renderer = instance_double(::Bosh::Template::Renderer)
13
+ expect(::Bosh::Template::Renderer).to receive(:new).with(context: '{}').and_return(bosh_renderer)
14
+ expect(bosh_renderer).to receive(:render).with('config.erb')
15
+ result = subject.render(context: {}, template: 'config.erb')
16
+ expect(result).to eq(template: 'config.erb', status: :success, detail: '')
17
+ end
18
+ end
19
+
20
+ context 'when the template refers to an unknown property' do
21
+ it 'reports the missing property' do
22
+ bosh_renderer = instance_double(::Bosh::Template::Renderer)
23
+ expect(::Bosh::Template::Renderer).to receive(:new).with(context: '{}').and_return(bosh_renderer)
24
+ expect(bosh_renderer).to receive(:render).with('config.erb').and_raise(::Bosh::Template::UnknownProperty.new('redis.port'))
25
+ result = subject.render(context: {}, template: 'config.erb')
26
+ expect(result).to eq(template: 'config.erb', status: :failure, detail: 'Missing property: redis.port')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,202 @@
1
+ require 'pathname'
2
+ require 'guard/compat/test/helper'
3
+
4
+ require 'guard/bosh/change_assessor'
5
+ require 'guard/bosh/job_repository'
6
+ require 'guard/bosh/template_checker'
7
+ require 'guard/bosh/notifier'
8
+
9
+ describe Guard::Bosh do
10
+ let(:change_assessor) { instance_double(Guard::Bosh::ChangeAssessor) }
11
+ let(:job_repository) { instance_double(Guard::Bosh::JobRepository) }
12
+ let(:template_checker) { instance_double(Guard::Bosh::TemplateChecker) }
13
+ let(:notifier) { instance_double(Guard::Bosh::Notifier) }
14
+ let(:deployment_manifest_path) { '/path/to/manifest.yml' }
15
+
16
+ subject do
17
+ Guard::Bosh.new(
18
+ deployment_manifest: deployment_manifest_path,
19
+ change_assessor: change_assessor,
20
+ job_repository: job_repository,
21
+ template_checker: template_checker,
22
+ notifier: notifier
23
+ )
24
+ end
25
+
26
+ context 'when a deployment manifest is not specified' do
27
+ it 'raises an error' do
28
+ expect { Guard::Bosh.new }.to raise_error(
29
+ 'Please specify the deployment_manifest in your Guardfile')
30
+ end
31
+ end
32
+
33
+ context 'when a template is modified' do
34
+ before do
35
+ expect(change_assessor).to receive(:determine_scope).with(
36
+ ['jobs/redis/templates/redis.conf.erb']).and_return(
37
+ [:single_template, 'redis'])
38
+ expect(job_repository).to receive(:find_by_template).with(
39
+ 'redis').and_return(%w(redis_leader_z1 redis_slave_z2))
40
+ end
41
+ context 'when there are no errors' do
42
+ it 'checks the template for errors' do
43
+ expect(template_checker).to receive(:check).with(
44
+ manifest_job_name: 'redis_leader_z1',
45
+ job_name: 'redis',
46
+ template: 'jobs/redis/templates/redis.conf.erb'
47
+ ).and_return([])
48
+ expect(template_checker).to receive(:check).with(
49
+ manifest_job_name: 'redis_slave_z2',
50
+ job_name: 'redis',
51
+ template: 'jobs/redis/templates/redis.conf.erb'
52
+ ).and_return([])
53
+ expect(notifier).to receive(:notify).with([]).once
54
+ subject.run_on_modifications(['jobs/redis/templates/redis.conf.erb'])
55
+ end
56
+ end
57
+ context 'when there are errors' do
58
+ it 'reports the errors' do
59
+ expect(template_checker).to receive(:check).with(
60
+ manifest_job_name: 'redis_leader_z1',
61
+ job_name: 'redis',
62
+ template: 'jobs/redis/templates/redis.conf.erb'
63
+ ).and_return([{
64
+ template: 'config.erb',
65
+ status: :failure,
66
+ detail: 'Missing property: redis.port'
67
+ }])
68
+ expect(template_checker).to receive(:check).with(
69
+ manifest_job_name: 'redis_slave_z2',
70
+ job_name: 'redis',
71
+ template: 'jobs/redis/templates/redis.conf.erb'
72
+ ).and_return([{
73
+ template: 'config.erb',
74
+ status: :failure,
75
+ detail: 'Missing property: redis.port'
76
+ }])
77
+ expect(notifier).to receive(:notify).with([
78
+ {
79
+ template: 'config.erb',
80
+ status: :failure,
81
+ detail: 'Missing property: redis.port'
82
+ },
83
+ {
84
+ template: 'config.erb',
85
+ status: :failure,
86
+ detail: 'Missing property: redis.port'
87
+ }
88
+ ]).once
89
+ expect(subject).to receive(:throw).with(:task_has_failed)
90
+ subject.run_on_modifications(['jobs/redis/templates/redis.conf.erb'])
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'when a job specification is modified' do
96
+ before do
97
+ expect(change_assessor).to receive(:determine_scope).with(
98
+ ['jobs/redis/spec']).and_return([:all_templates_for_job, 'redis'])
99
+
100
+ expect(job_repository).to receive(:find_by_template).with(
101
+ 'redis').and_return(%w(redis_leader_z1 redis_slave_z2))
102
+
103
+ expect(job_repository).to receive(:template_paths).with('redis').and_return([
104
+ 'jobs/redis/templates/redis_ctl.sh.erb',
105
+ 'jobs/redis/templates/redis.conf.erb'
106
+ ])
107
+ end
108
+ context 'when there are no errors' do
109
+ it 'checks the template for errors' do
110
+ expect(template_checker).to receive(:check).with(
111
+ manifest_job_name: 'redis_leader_z1',
112
+ job_name: 'redis',
113
+ template: 'jobs/redis/templates/redis_ctl.sh.erb'
114
+ ).and_return([])
115
+ expect(template_checker).to receive(:check).with(
116
+ manifest_job_name: 'redis_leader_z1',
117
+ job_name: 'redis',
118
+ template: 'jobs/redis/templates/redis.conf.erb'
119
+ ).and_return([])
120
+ expect(template_checker).to receive(:check).with(
121
+ manifest_job_name: 'redis_slave_z2',
122
+ job_name: 'redis',
123
+ template: 'jobs/redis/templates/redis_ctl.sh.erb'
124
+ ).and_return([])
125
+ expect(template_checker).to receive(:check).with(
126
+ manifest_job_name: 'redis_slave_z2',
127
+ job_name: 'redis',
128
+ template: 'jobs/redis/templates/redis.conf.erb'
129
+ ).and_return([])
130
+ expect(notifier).to receive(:notify).with([]).once
131
+ subject.run_on_modifications(['jobs/redis/spec'])
132
+ end
133
+ end
134
+ end
135
+
136
+ shared_context 'expect a complete check of all templates' do
137
+ before do
138
+ expect(job_repository).to receive(:job_templates).and_return(
139
+ %w(postgresql redis))
140
+ expect(job_repository).to receive(:template_paths).with('postgresql').and_return([
141
+ 'jobs/postgresql/templates/pg_hba.conf.erb'
142
+ ])
143
+ expect(job_repository).to receive(:template_paths).with('redis').and_return([
144
+ 'jobs/redis/templates/redis_ctl.sh.erb',
145
+ 'jobs/redis/templates/redis.conf.erb'
146
+ ])
147
+ expect(template_checker).to receive(:check).with(
148
+ manifest_job_name: 'redis_slave_z2',
149
+ job_name: 'redis',
150
+ template: 'jobs/redis/templates/redis.conf.erb'
151
+ ).and_return([])
152
+ expect(job_repository).to receive(:find_by_template).with(
153
+ 'redis').and_return(%w(redis_leader_z1 redis_slave_z2))
154
+ expect(job_repository).to receive(:find_by_template).with(
155
+ 'postgresql').and_return(['postgresql_z1'])
156
+ expect(template_checker).to receive(:check).with(
157
+ manifest_job_name: 'redis_leader_z1',
158
+ job_name: 'redis',
159
+ template: 'jobs/redis/templates/redis_ctl.sh.erb'
160
+ ).and_return([])
161
+ expect(template_checker).to receive(:check).with(
162
+ manifest_job_name: 'redis_leader_z1',
163
+ job_name: 'redis',
164
+ template: 'jobs/redis/templates/redis.conf.erb'
165
+ ).and_return([])
166
+ expect(template_checker).to receive(:check).with(
167
+ manifest_job_name: 'redis_slave_z2',
168
+ job_name: 'redis',
169
+ template: 'jobs/redis/templates/redis_ctl.sh.erb'
170
+ ).and_return([])
171
+ expect(template_checker).to receive(:check).with(
172
+ manifest_job_name: 'postgresql_z1',
173
+ job_name: 'postgresql',
174
+ template: 'jobs/postgresql/templates/pg_hba.conf.erb'
175
+ ).and_return([])
176
+ expect(notifier).to receive(:notify).with([]).once
177
+ end
178
+ end
179
+
180
+ context 'when the manifest is modified' do
181
+ include_context 'expect a complete check of all templates'
182
+ it 'checks all jobs and templates for errors' do
183
+ allow(subject).to receive(:reload_deployment_manifest)
184
+ expect(change_assessor).to receive(:determine_scope).with(
185
+ [deployment_manifest_path]).and_return([:all])
186
+ subject.run_on_modifications([deployment_manifest_path])
187
+ end
188
+ it 'reloads the manifest' do
189
+ allow(change_assessor).to receive(:determine_scope).with(
190
+ [deployment_manifest_path]).and_return([:all])
191
+ expect(subject).to receive(:reload_deployment_manifest)
192
+ subject.run_on_modifications([deployment_manifest_path])
193
+ end
194
+ end
195
+
196
+ context 'when the user requests that all templates are checked' do
197
+ include_context 'expect a complete check of all templates'
198
+ it 'checks all jobs and templates for errors' do
199
+ subject.run_all
200
+ end
201
+ end
202
+ end