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