guard-bosh 0.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.
@@ -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