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.
- 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
|