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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ab9503fc851cdf19034ac1606debeacb0f2f085c
|
4
|
+
data.tar.gz: 533de1a40a7b769549221eac66627419d1782f41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c4e0d898885f104008bf467c695667211e986e8d787a167bba61f1c0d555ae23e700da025c920c5962620aa219ad5127b94beacc0541054d482c15edc0d38c1b
|
7
|
+
data.tar.gz: edabe6e92e3c1cebec6cb08739eca1bb149c512fed9d4b288bc19a34139bb6549cdd6654cd2f006f1691b0fe89b0c6ed9a3221ed59cda6e176cc3c9c2e92fb2e
|
data/lib/guard/bosh.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'guard/compat/plugin'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh < Plugin
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
require 'guard/bosh/apply_specification'
|
8
|
+
require 'guard/bosh/change_assessor'
|
9
|
+
require 'guard/bosh/effective_properties_calculator'
|
10
|
+
require 'guard/bosh/global_properties_loader'
|
11
|
+
require 'guard/bosh/job_default_properties_loader'
|
12
|
+
require 'guard/bosh/job_properties_loader'
|
13
|
+
require 'guard/bosh/job_repository'
|
14
|
+
require 'guard/bosh/notifier'
|
15
|
+
require 'guard/bosh/package_resolver'
|
16
|
+
require 'guard/bosh/template_checker'
|
17
|
+
require 'guard/bosh/template_renderer'
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
super
|
21
|
+
|
22
|
+
unless options.key?(:deployment_manifest)
|
23
|
+
fail 'Please specify the deployment_manifest in your Guardfile'
|
24
|
+
end
|
25
|
+
|
26
|
+
@deployment_manifest = Pathname.new(options[:deployment_manifest])
|
27
|
+
@change_assessor = options[:change_assessor]
|
28
|
+
@job_repository = options[:job_repository]
|
29
|
+
@template_checker = options[:template_checker]
|
30
|
+
@notifier = options[:notifier]
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
reload_deployment_manifest
|
35
|
+
@notifier = Notifier.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_all
|
39
|
+
errors = render_all_job_templates
|
40
|
+
notify_errors(errors)
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_on_modifications(paths)
|
44
|
+
change_scope, job_name = @change_assessor.determine_scope(paths)
|
45
|
+
errors = case change_scope
|
46
|
+
when :all
|
47
|
+
reload_deployment_manifest
|
48
|
+
render_all_job_templates
|
49
|
+
when :all_templates_for_job
|
50
|
+
render_templates_for_job(
|
51
|
+
job_name, @job_repository.template_paths(job_name))
|
52
|
+
when :single_template
|
53
|
+
render_templates_for_job(job_name, paths)
|
54
|
+
end
|
55
|
+
notify_errors(errors)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def reload_deployment_manifest
|
61
|
+
manifest = YAML.load_file(@deployment_manifest)
|
62
|
+
@change_assessor = ChangeAssessor.new(@deployment_manifest)
|
63
|
+
@job_repository = JobRepository.new(manifest)
|
64
|
+
@template_checker = TemplateChecker.build(
|
65
|
+
deployment_manifest: manifest,
|
66
|
+
release_dir: Pathname.new('.')
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def notify_errors(errors)
|
71
|
+
@notifier.notify(errors)
|
72
|
+
throw :task_has_failed unless errors.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def render_all_job_templates
|
76
|
+
@job_repository.job_templates.flat_map do |job_name|
|
77
|
+
render_templates_for_job(job_name,
|
78
|
+
@job_repository.template_paths(job_name))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def render_templates_for_job(job_name, templates)
|
83
|
+
manifest_jobs = @job_repository.find_by_template(job_name)
|
84
|
+
template_results = manifest_jobs.product(
|
85
|
+
templates).flat_map do |manifest_job, template|
|
86
|
+
@template_checker.check(
|
87
|
+
manifest_job_name: manifest_job,
|
88
|
+
job_name: job_name,
|
89
|
+
template: template)
|
90
|
+
end
|
91
|
+
template_results.select { |tr| tr[:status] == :failure }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh
|
5
|
+
class ApplySpecification
|
6
|
+
def initialize(deployment_manifest:, package_resolver:)
|
7
|
+
@manifest = deployment_manifest
|
8
|
+
@package_resolver = package_resolver
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate(properties:, job_name:)
|
12
|
+
{
|
13
|
+
'deployment' => @manifest['name'],
|
14
|
+
'configuration_hash' => '24292bab7264f00ada207768efa7018e2a2226fc',
|
15
|
+
'job' => job(job_name),
|
16
|
+
'packages' => packages(job_name),
|
17
|
+
'resource_pool' => resource_pool(job_name),
|
18
|
+
'networks' => network(job_name),
|
19
|
+
'index' => 0,
|
20
|
+
'properties' => properties,
|
21
|
+
'persistent_disk' => persistent_disk(job_name),
|
22
|
+
'rendered_templates_archive' => rendered_templates_archive
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def job(job_name)
|
29
|
+
manifest_job = manifest_job_with_name(job_name)
|
30
|
+
{
|
31
|
+
'name' => job_name,
|
32
|
+
'template' => manifest_job['template'] ||
|
33
|
+
manifest_job['templates'].first['name'],
|
34
|
+
'version' => '1',
|
35
|
+
'sha1' => 'c7f277de5b283e5ceffe55674dc56fad2257ecab',
|
36
|
+
'blobstore_id' => '8a66ab45-4831-4ce3-aa8f-313fe33a9891',
|
37
|
+
'templates' => templates(manifest_job)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def packages(job_name)
|
42
|
+
manifest_job = manifest_job_with_name(job_name)
|
43
|
+
all_packages = template_names(manifest_job).flat_map do |template|
|
44
|
+
@package_resolver.resolve(template)
|
45
|
+
end
|
46
|
+
all_packages.inject({}) do |result, package|
|
47
|
+
result.merge(
|
48
|
+
|
49
|
+
package => {
|
50
|
+
'name' => package,
|
51
|
+
'version' => '1.0',
|
52
|
+
'sha1' => 'b945ce51b3635bb0ebfb2207323514381bcee824',
|
53
|
+
'blobstore_id' => '608c41bc-d491-4773-9812-8f24276eace1'
|
54
|
+
}
|
55
|
+
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def templates(manifest_job)
|
61
|
+
template_names(manifest_job).map { |t| template(t) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def template_names(manifest_job)
|
65
|
+
if manifest_job.key?('template')
|
66
|
+
Array(manifest_job['template'])
|
67
|
+
else
|
68
|
+
manifest_job['templates'].map { |t| t['name'] }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def template(name)
|
73
|
+
{
|
74
|
+
'name' => name,
|
75
|
+
'version' => '1',
|
76
|
+
'sha1' => '88d6ea417857efda58916f9cb9bd5dd3a0f76f00',
|
77
|
+
'blobstore_id' => '2356dff1-18fd-4314-a9bd-199b9d6c5c45'
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def network(job_name)
|
82
|
+
manifest_job = manifest_job_with_name(job_name)
|
83
|
+
job_network = manifest_job['networks'].first
|
84
|
+
network_definition = @manifest['networks'].find do |n|
|
85
|
+
n['name'] == job_network['name']
|
86
|
+
end
|
87
|
+
{
|
88
|
+
job_network['name'] => {
|
89
|
+
'cloud_properties' => network_definition['subnets'].first['cloud_properties'],
|
90
|
+
'dns_record_name' => dns_record_name(
|
91
|
+
job_name, job_network['name'], @manifest['name']),
|
92
|
+
'ip' => ip_address(job_network, network_definition),
|
93
|
+
'netmask' => netmask(network_definition['subnets'].first['range']),
|
94
|
+
'default' => %w(dns gateway)
|
95
|
+
}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def ip_address(job_network, network_definition)
|
100
|
+
if job_network.key?('static_ips') &&
|
101
|
+
Array(job_network['static_ips']).any?
|
102
|
+
job_network['static_ips'].first
|
103
|
+
else
|
104
|
+
# We could be better here and calculate the dynamic address properly
|
105
|
+
network_definition['subnets'].first['range'].split('/').first
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def dns_record_name(job, network, deployment)
|
110
|
+
"0.#{job}.#{network}.#{deployment}.bosh".gsub('_', '-')
|
111
|
+
end
|
112
|
+
|
113
|
+
def netmask(range)
|
114
|
+
cidr = range.split('/').last
|
115
|
+
IPAddr.new('255.255.255.255').mask(cidr).to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
def resource_pool(job_name)
|
119
|
+
manifest_job = manifest_job_with_name(job_name)
|
120
|
+
job_pool = @manifest['resource_pools'].find do |pool|
|
121
|
+
pool['name'] == manifest_job['resource_pool']
|
122
|
+
end
|
123
|
+
job_pool.delete_if do |k, _|
|
124
|
+
! %w(cloud_properties name stemcell).include?(k)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def persistent_disk(job_name)
|
129
|
+
manifest_job = manifest_job_with_name(job_name)
|
130
|
+
manifest_job['persistent_disk']
|
131
|
+
end
|
132
|
+
|
133
|
+
def rendered_templates_archive
|
134
|
+
{
|
135
|
+
'sha1' => 'c299ead74faf9ee9b47b3548e5df427e3e9a2c70',
|
136
|
+
'blobstore_id' => '72fb06ef-0f40-4280-85e8-b5930e672308'
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def manifest_job_with_name(name)
|
141
|
+
@manifest['jobs'].find { |j| j['name'] == name }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Guard
|
2
|
+
class Bosh
|
3
|
+
class ChangeAssessor
|
4
|
+
def initialize(deployment_manifest)
|
5
|
+
@deployment_manifest = deployment_manifest
|
6
|
+
end
|
7
|
+
|
8
|
+
def determine_scope(raw_paths)
|
9
|
+
paths = raw_paths.map { |p| Pathname.new(p) }
|
10
|
+
return :all if paths.include?(@deployment_manifest)
|
11
|
+
|
12
|
+
jobs = paths.select { |p| spec_path?(p) }.map do |p|
|
13
|
+
p.dirname.basename.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
spec_scope = scope(jobs, :all_templates_for_job)
|
17
|
+
return spec_scope if spec_scope
|
18
|
+
|
19
|
+
jobs = paths.select { |p| template_path?(p) }.map do |t|
|
20
|
+
template_job(t)
|
21
|
+
end
|
22
|
+
template_scope = scope(jobs, :single_template)
|
23
|
+
return template_scope if template_scope
|
24
|
+
|
25
|
+
:none
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def spec_path?(path)
|
31
|
+
path.basename.to_s == 'spec'
|
32
|
+
end
|
33
|
+
|
34
|
+
def template_path?(path)
|
35
|
+
path.descend { |p| break true if p.basename.to_s == 'templates' }
|
36
|
+
end
|
37
|
+
|
38
|
+
def template_job(template_path)
|
39
|
+
template_path.ascend do |p|
|
40
|
+
break p.dirname.basename.to_s if p.basename.to_s == 'templates'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def scope(jobs, value)
|
45
|
+
if jobs.size > 1
|
46
|
+
:all
|
47
|
+
elsif jobs.any?
|
48
|
+
[value, jobs.first]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'deep_merge'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh
|
5
|
+
class EffectivePropertiesCalculator
|
6
|
+
def initialize(loaders:)
|
7
|
+
@loaders = loaders
|
8
|
+
end
|
9
|
+
|
10
|
+
def calculate_effective_properties(job)
|
11
|
+
@loaders.inject({}) do |result, loader|
|
12
|
+
result.deep_merge!(loader.load_properties(job))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Guard
|
2
|
+
class Bosh
|
3
|
+
class GlobalPropertiesLoader
|
4
|
+
def initialize(deployment_manifest:)
|
5
|
+
@deployment_manifest = deployment_manifest
|
6
|
+
end
|
7
|
+
|
8
|
+
def load_properties(_)
|
9
|
+
global_properties = @deployment_manifest['properties']
|
10
|
+
if global_properties.nil?
|
11
|
+
{}
|
12
|
+
else
|
13
|
+
global_properties
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh
|
5
|
+
class JobDefaultPropertiesLoader
|
6
|
+
def initialize(release_dir:)
|
7
|
+
@release_dir = release_dir
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_properties(job)
|
11
|
+
properties = job_spec(job[:job_name]).fetch('properties')
|
12
|
+
expand(
|
13
|
+
intermediate_properties(properties).merge(
|
14
|
+
default_properties(properties)
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def default_properties(properties)
|
22
|
+
defaults_only = properties.map do |property, config|
|
23
|
+
[property, config['default']]
|
24
|
+
end
|
25
|
+
Hash[defaults_only].reject { |_k, v| v.nil? }
|
26
|
+
end
|
27
|
+
|
28
|
+
def intermediate_properties(properties)
|
29
|
+
intermediates = properties.keys.map do |key|
|
30
|
+
key.split('.')[0..-2].join('.')
|
31
|
+
end.sort.uniq
|
32
|
+
Hash[intermediates.zip(intermediates.map { |_t| {} })]
|
33
|
+
end
|
34
|
+
|
35
|
+
def job_spec(job_name)
|
36
|
+
YAML.load_file(@release_dir + 'jobs' + job_name + 'spec')
|
37
|
+
end
|
38
|
+
|
39
|
+
def expand(properties)
|
40
|
+
properties.each_with_object({}) do |property, expanded|
|
41
|
+
key, value = property
|
42
|
+
current = expanded
|
43
|
+
each_parent(key) do |parent|
|
44
|
+
current[parent] = {} unless current.key?(parent)
|
45
|
+
current = current[parent]
|
46
|
+
end
|
47
|
+
current[leaf(key)] = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def each_parent(key)
|
52
|
+
path = key.split('.')
|
53
|
+
path[0...-1].each do |parent|
|
54
|
+
yield parent
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def leaf(key)
|
59
|
+
key.split('.').last
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Guard
|
2
|
+
class Bosh
|
3
|
+
class JobPropertiesLoader
|
4
|
+
def initialize(deployment_manifest:)
|
5
|
+
@manifest = deployment_manifest
|
6
|
+
end
|
7
|
+
|
8
|
+
def load_properties(job)
|
9
|
+
manifest_job = @manifest['jobs'].find do |j|
|
10
|
+
j['name'] == job[:manifest_job_name]
|
11
|
+
end
|
12
|
+
if manifest_job
|
13
|
+
manifest_job['properties']
|
14
|
+
else
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class Bosh
|
5
|
+
class JobRepository
|
6
|
+
def initialize(deployment_manifest)
|
7
|
+
@manifest = deployment_manifest
|
8
|
+
end
|
9
|
+
|
10
|
+
def job_templates
|
11
|
+
@manifest['jobs'].flat_map { |j| template_names(j) }.sort.uniq
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_by_template(job)
|
15
|
+
jobs_using_template = @manifest['jobs'].select do |manifest_job|
|
16
|
+
template_names(manifest_job).include?(job)
|
17
|
+
end
|
18
|
+
jobs_using_template.map { |j| j['name'] }
|
19
|
+
end
|
20
|
+
|
21
|
+
def template_paths(job)
|
22
|
+
job_dir = Pathname.new('jobs') + job
|
23
|
+
YAML.load_file(job_dir + 'spec')['templates'].keys.map do |template|
|
24
|
+
job_dir + 'templates' + template
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def template_names(manifest_job)
|
31
|
+
if manifest_job.key?('templates')
|
32
|
+
manifest_job['templates'].map { |t| t['name'] }
|
33
|
+
else
|
34
|
+
Array(manifest_job['template'])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|