guard-bosh 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/guard/bosh.rb +94 -0
- data/lib/guard/bosh/apply_specification.rb +145 -0
- data/lib/guard/bosh/change_assessor.rb +53 -0
- data/lib/guard/bosh/effective_properties_calculator.rb +17 -0
- data/lib/guard/bosh/global_properties_loader.rb +18 -0
- data/lib/guard/bosh/job_default_properties_loader.rb +63 -0
- data/lib/guard/bosh/job_properties_loader.rb +20 -0
- data/lib/guard/bosh/job_repository.rb +39 -0
- data/lib/guard/bosh/notifier.rb +19 -0
- data/lib/guard/bosh/package_resolver.rb +14 -0
- data/lib/guard/bosh/template_checker.rb +44 -0
- data/lib/guard/bosh/template_renderer.rb +24 -0
- data/lib/guard/bosh/templates/Guardfile +10 -0
- data/spec/guard/bosh/apply_specification_spec.rb +184 -0
- data/spec/guard/bosh/change_assessor_spec.rb +65 -0
- data/spec/guard/bosh/effective_properties_calculator_spec.rb +64 -0
- data/spec/guard/bosh/global_properties_loader_spec.rb +38 -0
- data/spec/guard/bosh/job_default_properties_loader_spec.rb +69 -0
- data/spec/guard/bosh/job_properties_loader_spec.rb +50 -0
- data/spec/guard/bosh/job_repository_spec.rb +56 -0
- data/spec/guard/bosh/notifier_spec.rb +40 -0
- data/spec/guard/bosh/package_resolver_spec.rb +23 -0
- data/spec/guard/bosh/template_checker_spec.rb +67 -0
- data/spec/guard/bosh/template_renderer_spec.rb +29 -0
- data/spec/guard/bosh_spec.rb +202 -0
- data/spec/spec_helper.rb +7 -0
- metadata +168 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'guard/compat'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh
|
5
|
+
class Notifier
|
6
|
+
def notify(errors)
|
7
|
+
if errors.empty?
|
8
|
+
Guard::Compat::UI.notify(
|
9
|
+
'Succeeded', title: 'BOSH', image: :success, priority: -2)
|
10
|
+
else
|
11
|
+
Guard::Compat::UI.error(
|
12
|
+
'%s: %s' % [errors.first[:template], errors.first[:detail]])
|
13
|
+
Guard::Compat::UI.notify(
|
14
|
+
'Failed', title: 'BOSH', image: :failed, priority: 2)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Guard
|
2
|
+
class Bosh
|
3
|
+
class PackageResolver
|
4
|
+
def initialize(release_dir)
|
5
|
+
@release_dir = release_dir
|
6
|
+
end
|
7
|
+
|
8
|
+
def resolve(job)
|
9
|
+
job_spec = @release_dir + 'jobs' + job + 'spec'
|
10
|
+
YAML.load_file(job_spec)['packages']
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Guard
|
2
|
+
class Bosh
|
3
|
+
class TemplateChecker
|
4
|
+
def initialize(deployment_manifest:,
|
5
|
+
properties_calculator:,
|
6
|
+
apply_specification:,
|
7
|
+
template_renderer:)
|
8
|
+
@deployment_manifest = deployment_manifest
|
9
|
+
@properties_calculator = properties_calculator
|
10
|
+
@apply_specification = apply_specification
|
11
|
+
@template_renderer = template_renderer
|
12
|
+
end
|
13
|
+
|
14
|
+
def check(manifest_job_name:, job_name:, template:)
|
15
|
+
properties = @properties_calculator.calculate_effective_properties(
|
16
|
+
manifest_job_name: manifest_job_name, job_name: job_name)
|
17
|
+
apply_spec = @apply_specification.generate(
|
18
|
+
properties: properties,
|
19
|
+
job_name: manifest_job_name
|
20
|
+
)
|
21
|
+
@template_renderer.render(context: apply_spec, template: template)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.build(deployment_manifest:, release_dir:)
|
25
|
+
properties_calculator = EffectivePropertiesCalculator.new(loaders: [
|
26
|
+
JobDefaultPropertiesLoader.new(release_dir: release_dir),
|
27
|
+
GlobalPropertiesLoader.new(deployment_manifest: deployment_manifest),
|
28
|
+
JobPropertiesLoader.new(deployment_manifest: deployment_manifest)
|
29
|
+
])
|
30
|
+
apply_specification = ApplySpecification.new(
|
31
|
+
deployment_manifest: deployment_manifest,
|
32
|
+
package_resolver: PackageResolver.new(release_dir)
|
33
|
+
)
|
34
|
+
template_renderer = TemplateRenderer.new
|
35
|
+
new(
|
36
|
+
deployment_manifest: deployment_manifest,
|
37
|
+
properties_calculator: properties_calculator,
|
38
|
+
apply_specification: apply_specification,
|
39
|
+
template_renderer: template_renderer
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'bosh/template/renderer'
|
3
|
+
require 'bosh/template/unknown_property'
|
4
|
+
|
5
|
+
module Guard
|
6
|
+
class Bosh
|
7
|
+
class TemplateRenderer
|
8
|
+
def render(context:, template:)
|
9
|
+
renderer = ::Bosh::Template::Renderer.new(
|
10
|
+
context: JSON.generate(context))
|
11
|
+
begin
|
12
|
+
renderer.render(template)
|
13
|
+
{ template: template, status: :success, detail: '' }
|
14
|
+
rescue ::Bosh::Template::UnknownProperty => e
|
15
|
+
{
|
16
|
+
template: template,
|
17
|
+
status: :failure,
|
18
|
+
detail: "Missing property: #{e.name}"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'guard/bosh/apply_specification'
|
2
|
+
|
3
|
+
describe Guard::Bosh::ApplySpecification do
|
4
|
+
let(:package_resolver) { double }
|
5
|
+
subject do
|
6
|
+
Guard::Bosh::ApplySpecification.new(
|
7
|
+
deployment_manifest: manifest_stub,
|
8
|
+
package_resolver: package_resolver
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:manifest_stub) do
|
13
|
+
{
|
14
|
+
'name' => 'redis-deployment',
|
15
|
+
'jobs' => [{
|
16
|
+
'instances' => 1,
|
17
|
+
'name' => 'redis_leader_z1',
|
18
|
+
'networks' => [{ 'name' => 'redis1', 'static_ips' => ['203.0.113.2'] }],
|
19
|
+
'persistent_disk' => 0,
|
20
|
+
'resource_pool' => 'small_z1',
|
21
|
+
'templates' => [{ 'name' => 'redis', 'release' => 'redis' }]
|
22
|
+
}],
|
23
|
+
'networks' => [
|
24
|
+
{
|
25
|
+
'name' => 'redis1',
|
26
|
+
'subnets' => [{
|
27
|
+
'cloud_properties' => { 'name' => 'iaas_network_name' },
|
28
|
+
'range' => '203.0.113.2/24',
|
29
|
+
'static' => ['10.244.2.2']
|
30
|
+
}]
|
31
|
+
}
|
32
|
+
],
|
33
|
+
'resource_pools' => [
|
34
|
+
{
|
35
|
+
'cloud_properties' => { 'name' => 'random' },
|
36
|
+
'name' => 'small_z1',
|
37
|
+
'network' => 'redis1',
|
38
|
+
'size' => 3,
|
39
|
+
'stemcell' => {
|
40
|
+
'name' => 'bosh-warden-boshlite-ubuntu-trusty-go_agent',
|
41
|
+
'version' => '389'
|
42
|
+
}
|
43
|
+
}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
before do
|
49
|
+
allow(package_resolver).to receive(:resolve).with('redis').and_return(['redis'])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'includes the effective properties provided' do
|
53
|
+
effective_properties = {
|
54
|
+
'redis' => {
|
55
|
+
'master' => '203.0.113.2'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
apply_spec = subject.generate(
|
59
|
+
properties: effective_properties, job_name: 'redis_leader_z1')
|
60
|
+
expect(apply_spec['properties']).to include(effective_properties)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'includes a job index' do
|
64
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
65
|
+
expect(apply_spec['index']).to eq(0)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'includes the job details' do
|
69
|
+
expected_job = {
|
70
|
+
'name' => 'redis_leader_z1',
|
71
|
+
'template' => 'redis',
|
72
|
+
'version' => '1',
|
73
|
+
'sha1' => 'c7f277de5b283e5ceffe55674dc56fad2257ecab',
|
74
|
+
'blobstore_id' => '8a66ab45-4831-4ce3-aa8f-313fe33a9891',
|
75
|
+
'templates' => [
|
76
|
+
{
|
77
|
+
'name' => 'redis',
|
78
|
+
'version' => '1',
|
79
|
+
'sha1' => '88d6ea417857efda58916f9cb9bd5dd3a0f76f00',
|
80
|
+
'blobstore_id' => '2356dff1-18fd-4314-a9bd-199b9d6c5c45'
|
81
|
+
}
|
82
|
+
]
|
83
|
+
}
|
84
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
85
|
+
expect(apply_spec['job']).to eq(expected_job)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'includes the package details' do
|
89
|
+
expected_packages = {
|
90
|
+
'redis' => {
|
91
|
+
'name' => 'redis',
|
92
|
+
'version' => '1.0',
|
93
|
+
'sha1' => 'b945ce51b3635bb0ebfb2207323514381bcee824',
|
94
|
+
'blobstore_id' => '608c41bc-d491-4773-9812-8f24276eace1'
|
95
|
+
}
|
96
|
+
}
|
97
|
+
expect(package_resolver).to receive(:resolve).with('redis').and_return(['redis'])
|
98
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
99
|
+
expect(apply_spec['packages']).to eq(expected_packages)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'includes a dummy configuration hash' do
|
103
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
104
|
+
expect(apply_spec['configuration_hash']).to_not be_empty
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'when the job has a static ip' do
|
108
|
+
it 'includes the network config' do
|
109
|
+
expected_networks = {
|
110
|
+
'redis1' => {
|
111
|
+
'cloud_properties' => { 'name' => 'iaas_network_name' },
|
112
|
+
'dns_record_name' => '0.redis-leader-z1.redis1.redis-deployment.bosh',
|
113
|
+
'ip' => '203.0.113.2',
|
114
|
+
'netmask' => '255.255.255.0',
|
115
|
+
'default' => %w(dns gateway)
|
116
|
+
}
|
117
|
+
}
|
118
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
119
|
+
expect(apply_spec['networks']).to eq(expected_networks)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when the job has a dynamic ip' do
|
124
|
+
let(:manifest_stub_without_static_ip) do
|
125
|
+
manifest_stub.tap do |stub|
|
126
|
+
stub['jobs'].first['networks'].first.delete('static_ips')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
subject do
|
131
|
+
Guard::Bosh::ApplySpecification.new(
|
132
|
+
deployment_manifest: manifest_stub_without_static_ip,
|
133
|
+
package_resolver: package_resolver
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'includes the network config' do
|
138
|
+
expected_networks = {
|
139
|
+
'redis1' => {
|
140
|
+
'cloud_properties' => { 'name' => 'iaas_network_name' },
|
141
|
+
'dns_record_name' => '0.redis-leader-z1.redis1.redis-deployment.bosh',
|
142
|
+
'ip' => '203.0.113.2',
|
143
|
+
'netmask' => '255.255.255.0',
|
144
|
+
'default' => %w(dns gateway)
|
145
|
+
}
|
146
|
+
}
|
147
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
148
|
+
expect(apply_spec['networks']).to eq(expected_networks)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'includes the resource pool' do
|
153
|
+
resource_pool = {
|
154
|
+
'cloud_properties' => {
|
155
|
+
'name' => 'random'
|
156
|
+
},
|
157
|
+
'name' => 'small_z1',
|
158
|
+
'stemcell' => {
|
159
|
+
'name' => 'bosh-warden-boshlite-ubuntu-trusty-go_agent',
|
160
|
+
'version' => '389'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
164
|
+
expect(apply_spec['resource_pool']).to eq(resource_pool)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'includes the deployment name' do
|
168
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
169
|
+
expect(apply_spec['deployment']).to eq('redis-deployment')
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'includes the persistent disk' do
|
173
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
174
|
+
expect(apply_spec['persistent_disk']).to eq(0)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'includes a dummy rendered templates archive' do
|
178
|
+
apply_spec = subject.generate(properties: {}, job_name: 'redis_leader_z1')
|
179
|
+
expect(apply_spec['rendered_templates_archive']).to eq(
|
180
|
+
'sha1' => 'c299ead74faf9ee9b47b3548e5df427e3e9a2c70',
|
181
|
+
'blobstore_id' => '72fb06ef-0f40-4280-85e8-b5930e672308'
|
182
|
+
)
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'guard/bosh/change_assessor'
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
describe Guard::Bosh::ChangeAssessor do
|
6
|
+
subject do
|
7
|
+
Guard::Bosh::ChangeAssessor.new(Pathname.new('templates/manifest.yml'))
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when a final.yml has been modified' do
|
11
|
+
it 'reports a scope that means the change can be ignored' do
|
12
|
+
change_scope, _ = subject.determine_scope(['config/final.yml'])
|
13
|
+
expect(change_scope).to eq(:none)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when the deployment manifest has been modified' do
|
18
|
+
it 'reports a scope that means all jobs and templates must be re-evaluated' do
|
19
|
+
change_scope, _ = subject.determine_scope(['templates/manifest.yml'])
|
20
|
+
expect(change_scope).to eq(:all)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when a job spec has been modified' do
|
25
|
+
it 'reports a scope that means all templates must be evaluated for a single job' do
|
26
|
+
change_scope, _ = subject.determine_scope(['jobs/redis/spec'])
|
27
|
+
expect(change_scope).to eq(:all_templates_for_job)
|
28
|
+
end
|
29
|
+
it 'reports the correct job name' do
|
30
|
+
_, job_name = subject.determine_scope(['jobs/redis/spec'])
|
31
|
+
expect(job_name).to eq('redis')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when a job template has been modified' do
|
36
|
+
it 'reports a scope that means a single template must be evaluated for a single job' do
|
37
|
+
change_scope, _ = subject.determine_scope(['jobs/redis/templates/config/redis.conf.erb'])
|
38
|
+
expect(change_scope).to eq(:single_template)
|
39
|
+
end
|
40
|
+
it 'reports the correct job name' do
|
41
|
+
_, job_name = subject.determine_scope(['jobs/redis/templates/config/redis.conf.erb'])
|
42
|
+
expect(job_name).to eq('redis')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when multiple job specs have been modified across jobs' do
|
47
|
+
it 'reports a scope that means all jobs and templates must be re-evaluated' do
|
48
|
+
change_scope, _ = subject.determine_scope([
|
49
|
+
'jobs/redis/spec',
|
50
|
+
'jobs/postgresql/spec'
|
51
|
+
])
|
52
|
+
expect(change_scope).to eq(:all)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when multiple templates have been modified across jobs' do
|
57
|
+
it 'reports a scope that means all jobs and templates must be re-evaluated' do
|
58
|
+
change_scope, _ = subject.determine_scope([
|
59
|
+
'jobs/redis/templates/config/redis.conf.erb',
|
60
|
+
'jobs/postgresql/templates/config/pg_hba.conf.erb'
|
61
|
+
])
|
62
|
+
expect(change_scope).to eq(:all)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'guard/bosh/job_properties_loader'
|
2
|
+
require 'guard/bosh/job_default_properties_loader'
|
3
|
+
require 'guard/bosh/global_properties_loader'
|
4
|
+
require 'guard/bosh/effective_properties_calculator'
|
5
|
+
|
6
|
+
describe Guard::Bosh::EffectivePropertiesCalculator do
|
7
|
+
let(:job_defaults_loader) do
|
8
|
+
instance_double(Guard::Bosh::JobDefaultPropertiesLoader)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:global_properties_loader) do
|
12
|
+
instance_double(Guard::Bosh::GlobalPropertiesLoader)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:job_properties_loader) do
|
16
|
+
instance_double(Guard::Bosh::JobPropertiesLoader)
|
17
|
+
end
|
18
|
+
|
19
|
+
subject do
|
20
|
+
Guard::Bosh::EffectivePropertiesCalculator.new(loaders: [
|
21
|
+
job_defaults_loader,
|
22
|
+
global_properties_loader,
|
23
|
+
job_properties_loader
|
24
|
+
])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'merges the properties' do
|
28
|
+
expect(job_defaults_loader).to receive(:load_properties).and_return(
|
29
|
+
|
30
|
+
'redis' => {
|
31
|
+
'password' => 'password',
|
32
|
+
'port' => 6379
|
33
|
+
}
|
34
|
+
|
35
|
+
)
|
36
|
+
expect(global_properties_loader).to receive(:load_properties).and_return(
|
37
|
+
|
38
|
+
'redis' => {
|
39
|
+
'password' => 'secure-password',
|
40
|
+
'slaves' => ['203.0.113.3', '203.0.113.4']
|
41
|
+
},
|
42
|
+
'network' => 'redis1'
|
43
|
+
|
44
|
+
)
|
45
|
+
expect(job_properties_loader).to receive(:load_properties).and_return(
|
46
|
+
|
47
|
+
'redis' => {
|
48
|
+
'master' => '203.0.113.2'
|
49
|
+
}
|
50
|
+
|
51
|
+
)
|
52
|
+
|
53
|
+
properties = subject.calculate_effective_properties(manifest_job_name: 'redis_z1')
|
54
|
+
expect(properties).to eq(
|
55
|
+
'redis' => {
|
56
|
+
'password' => 'secure-password',
|
57
|
+
'port' => 6379,
|
58
|
+
'master' => '203.0.113.2',
|
59
|
+
'slaves' => ['203.0.113.3', '203.0.113.4']
|
60
|
+
},
|
61
|
+
'network' => 'redis1'
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'guard/bosh/global_properties_loader'
|
2
|
+
|
3
|
+
describe Guard::Bosh::GlobalPropertiesLoader do
|
4
|
+
let(:deployment_manifest) do
|
5
|
+
{
|
6
|
+
'jobs' => %w(some jobs),
|
7
|
+
'networks' => %w(some networks),
|
8
|
+
'properties' => {
|
9
|
+
'global' => 'property'
|
10
|
+
}
|
11
|
+
}
|
12
|
+
end
|
13
|
+
let(:deployment_manifest_without_global_properties) do
|
14
|
+
deployment_manifest.tap do |manifest|
|
15
|
+
manifest.delete('properties')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
subject do
|
19
|
+
Guard::Bosh::GlobalPropertiesLoader.new(
|
20
|
+
deployment_manifest: deployment_manifest)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when the manifest contains global properties section' do
|
24
|
+
it 'returns them' do
|
25
|
+
expect(subject.load_properties('ignored')).to eq('global' => 'property')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when the manifest does not contain a global properties section' do
|
30
|
+
subject do
|
31
|
+
Guard::Bosh::GlobalPropertiesLoader.new(
|
32
|
+
deployment_manifest: deployment_manifest_without_global_properties)
|
33
|
+
end
|
34
|
+
it 'returns an empty' do
|
35
|
+
expect(subject.load_properties('ignored')).to be_empty
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|