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