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,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,10 @@
1
+ clearing :on
2
+ directories %w(jobs templates)
3
+
4
+ manifest_path = 'path/to/manifest.yml'
5
+ guard :bosh, deployment_manifest: manifest_path do
6
+ watch(%r{^jobs/.*})
7
+ watch(manifest_path)
8
+ end
9
+
10
+ notification :tmux, display_message: true
@@ -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