bosh-template 0.0.1

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: 09977444d10e0c549c9b8bf1308937c64af24044
4
+ data.tar.gz: 170dd775f88c18db31cabdd6e4f04dc0154b42ac
5
+ SHA512:
6
+ metadata.gz: 5d5790815e3644abd9fbe7ada1e3f750eee5e00c7a883bbf900f8bf30853ac71f26b9cb9ccf7556e73e03d9eee9c941efc39cba3b56fa7daec1ca0f6669550a7
7
+ data.tar.gz: b50b9726628cda738bc2b615c984ecff936c4ea7c15149422543568e1a800cf277a482d19759f748d7392e447caaa10379729ef11489092cd468caab7c5a1dc7
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # bosh-template
2
+
3
+ Renders BOSH templates
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ gem install bosh-template
9
+
10
+ bosh-template <template.erb> --context '{ "...": ... }'
11
+ ```
12
+
13
+ ## Unit-testing your release ERB templates
14
+
15
+ The `Bosh::Template::Test` package provides classes to unit-test your templates. These classes can be used to mock out different combinations of links, instances, etc., without the need to script a `create-release` and `deploy` against a running director.
16
+
17
+ Examples of usage are provided in `src/spec/config.erb_spec.rb` in `template-test-release`.
18
+
19
+ When you create your own release, you can likewise create a tests folder in your release and use these classes. A release author can create a test and call the template rendering like such:
20
+
21
+ ```ruby
22
+ let(:job) {release.job('JOB-NAME')} # e.g. 'web-server;
23
+ let(:template) {job.template('PATH-TO-TEMPLATE')} # e.g. 'config/config-with-nested'
24
+ let(:manifest) {
25
+ 'cert' => '----- BEGIN ... -----',
26
+ 'port' => 42,
27
+ }
28
+ let(:instance) { InstanceSpec.new(name:'instance-name', az: 'az1', bootstrap: true) }
29
+ let(:link_instance) { InstanceSpec.new(name:'link-instance-name', az: 'az2') }
30
+ let(:link_properties){
31
+ 'link-key' => 'link-value',
32
+ }
33
+ let(:link) { Link.new(name:'link-name', instances:[link_instance], properties: link_properties)}
34
+
35
+
36
+ rendered_template = JSON.parse(template.render(manifest, spec: instance, consumes: [link]))
37
+ ```
38
+
39
+ And then check that their template rendered as they expected.
data/bin/bosh-template ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bosh/template/renderer'
4
+ require 'optparse'
5
+
6
+ template_name = ARGV[0]
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ opts.banner = 'Usage: bosh-templates template.erb [options]'
11
+ opts.separator('Command-line renderer BOSH templates')
12
+
13
+ opts.separator('')
14
+ opts.separator('Options:')
15
+
16
+ opts.on('-C', '--context STRING', 'JSON string containing property values for template context') do |v|
17
+ options[:context] = v
18
+ end
19
+ end.parse!
20
+
21
+ raise OptionParser::MissingArgument.new('--context') if options[:context].nil?
22
+
23
+ renderer = Bosh::Template::Renderer.new(options)
24
+ puts renderer.render(template_name)
@@ -0,0 +1,7 @@
1
+ module Bosh
2
+ module Template
3
+ end
4
+ end
5
+
6
+ require 'bosh/template/evaluation_context'
7
+ require 'bosh/template/test'
@@ -0,0 +1,218 @@
1
+ require 'ostruct'
2
+ require 'bosh/template/evaluation_failed'
3
+ require 'bosh/template/unknown_property'
4
+ require 'bosh/template/unknown_link'
5
+ require 'bosh/template/property_helper'
6
+ require 'bosh/template/evaluation_link_instance'
7
+ require 'bosh/template/evaluation_link'
8
+ require 'bosh/template/manual_link_dns_encoder'
9
+
10
+ module Bosh
11
+ module Template
12
+ # Helper class to evaluate templates. Used by Director, CLI and Agent.
13
+ class EvaluationContext
14
+ include PropertyHelper
15
+
16
+ # @return [String] Template name
17
+ attr_reader :name
18
+
19
+ # @return [Integer] Template instance index
20
+ attr_reader :index
21
+
22
+ # @return [Hash] Template properties
23
+ attr_reader :properties
24
+
25
+ # @return [Hash] Raw template properties (no openstruct)
26
+ attr_reader :raw_properties
27
+
28
+ # @return [Hash] Template spec
29
+ attr_reader :spec
30
+
31
+ # @param [Hash] spec Template spec
32
+ def initialize(spec, dns_encoder)
33
+ unless spec.is_a?(Hash)
34
+ raise EvaluationFailed,
35
+ 'Invalid spec provided for template evaluation context, ' +
36
+ "Hash expected, #{spec.class} given"
37
+ end
38
+
39
+ if spec['job'].is_a?(Hash)
40
+ @name = spec['job']['name']
41
+ else
42
+ @name = nil
43
+ end
44
+
45
+ @index = spec['index']
46
+ @spec = openstruct(spec, BackCompatOpenStruct)
47
+ @raw_properties = spec['properties'] || {}
48
+ @properties = openstruct(@raw_properties)
49
+ @dns_encoder = dns_encoder
50
+
51
+ @links = spec['links'] || {}
52
+ end
53
+
54
+ # @return [Binding] Template binding
55
+ def get_binding
56
+ binding.taint
57
+ end
58
+
59
+ # Property lookup helper
60
+ #
61
+ # @overload p(name, default_value)
62
+ # Returns property value or default value if property not set
63
+ # @param [String] name Property name
64
+ # @param [Object] default_value Default value
65
+ # @return [Object] Property value
66
+ #
67
+ # @overload p(names, default_value)
68
+ # Returns first property from the list that is set or default value if
69
+ # none of them are set
70
+ # @param [Array<String>] names Property names
71
+ # @param [Object] default_value Default value
72
+ # @return [Object] Property value
73
+ #
74
+ # @overload p(names)
75
+ # Looks up first property from the list that is set, raises an error
76
+ # if none of them are set.
77
+ # @param [Array<String>] names Property names
78
+ # @return [Object] Property value
79
+ # @raise [Bosh::Common::UnknownProperty]
80
+ #
81
+ # @overload p(name)
82
+ # Looks up property and raises an error if it's not set
83
+ # @param [String] name Property name
84
+ # @return [Object] Property value
85
+ # @raise [Bosh::Common::UnknownProperty]
86
+ def p(*args)
87
+ names = Array(args[0])
88
+
89
+ names.each do |name|
90
+ result = lookup_property(@raw_properties, name)
91
+ return result unless result.nil?
92
+ end
93
+
94
+ return args[1] if args.length == 2
95
+ raise UnknownProperty.new(names)
96
+ end
97
+
98
+ def link(name)
99
+ link_spec = lookup_property(@links, name)
100
+ raise UnknownLink.new(name) if link_spec.nil?
101
+
102
+ if link_spec.has_key?('instances')
103
+ link_instances = link_spec['instances'].map do |instance_link_spec|
104
+ EvaluationLinkInstance.new(instance_link_spec['name'], instance_link_spec['index'], instance_link_spec['id'], instance_link_spec['az'], instance_link_spec['address'], instance_link_spec['properties'], instance_link_spec['bootstrap'])
105
+ end
106
+
107
+ if link_spec.has_key?('address')
108
+ encoder_to_inject = ManualLinkDnsEncoder.new(link_spec['address'])
109
+ else
110
+ encoder_to_inject = @dns_encoder
111
+ end
112
+
113
+ return EvaluationLink.new(
114
+ link_instances,
115
+ link_spec['properties'],
116
+ link_spec['instance_group'],
117
+ link_spec['default_network'],
118
+ link_spec['deployment_name'],
119
+ link_spec['domain'],
120
+ encoder_to_inject,
121
+ )
122
+ end
123
+ raise UnknownLink.new(name)
124
+ end
125
+
126
+ # Run a block of code if all given properties are defined
127
+ # @param [Array<String>] names Property names
128
+ # @yield [Object] property values
129
+ def if_p(*names)
130
+ values = names.map do |name|
131
+ value = lookup_property(@raw_properties, name)
132
+ return ActiveElseBlock.new(self) if value.nil?
133
+ value
134
+ end
135
+
136
+ yield *values
137
+ InactiveElseBlock.new
138
+ end
139
+
140
+ # Run a block of code if the link given exists
141
+ # @param [String] name of the link
142
+ # @yield [Object] link, which is an array of instances
143
+ def if_link(name)
144
+ link_spec = lookup_property(@links, name)
145
+ if link_spec.nil? || !link_spec.has_key?('instances')
146
+ return ActiveElseBlock.new(self)
147
+ else
148
+ link_instances = link_spec['instances'].map do |instance_link_spec|
149
+ EvaluationLinkInstance.new(instance_link_spec['name'], instance_link_spec['index'], instance_link_spec['id'], instance_link_spec['az'], instance_link_spec['address'], instance_link_spec['properties'], instance_link_spec['bootstrap'])
150
+ end
151
+
152
+ yield EvaluationLink.new(link_instances, link_spec['properties'], link_spec['instance_group'], link_spec['default_network'], link_spec['deployment_name'], link_spec['root_domain'], @dns_encoder)
153
+ InactiveElseBlock.new
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ # @return [Object] Object representation where all hashes are unrolled
160
+ # into OpenStruct objects. This exists mostly for backward
161
+ # compatibility, as it doesn't provide good error reporting.
162
+ def openstruct(object, open_struct_klass=OpenStruct)
163
+ case object
164
+ when Hash
165
+ mapped = object.inject({}) do |h, (k, v)|
166
+ h[k] = openstruct(v, open_struct_klass); h
167
+ end
168
+ open_struct_klass.new(mapped)
169
+ when Array
170
+ object.map { |item| openstruct(item, open_struct_klass) }
171
+ else
172
+ object
173
+ end
174
+ end
175
+
176
+ class BackCompatOpenStruct < OpenStruct
177
+ def methods(regular=true)
178
+ if regular
179
+ super(regular)
180
+ else
181
+ marshal_dump.keys.map(&:to_sym)
182
+ end
183
+ end
184
+ end
185
+
186
+ class ActiveElseBlock
187
+ def initialize(template_context)
188
+ @context = template_context
189
+ end
190
+
191
+ def else
192
+ yield
193
+ end
194
+
195
+ def else_if_p(*names, &block)
196
+ @context.if_p(*names, &block)
197
+ end
198
+
199
+ def else_if_link(name, &block)
200
+ @context.if_link(name, &block)
201
+ end
202
+ end
203
+
204
+ class InactiveElseBlock
205
+ def else
206
+ end
207
+
208
+ def else_if_p(*names)
209
+ InactiveElseBlock.new
210
+ end
211
+
212
+ def else_if_link(name)
213
+ InactiveElseBlock.new
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,6 @@
1
+ module Bosh
2
+ module Template
3
+ class EvaluationFailed < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,58 @@
1
+ require 'bosh/template/property_helper'
2
+
3
+ module Bosh
4
+ module Template
5
+ class EvaluationLink
6
+ include PropertyHelper
7
+
8
+ attr_reader :instances
9
+ attr_reader :properties
10
+
11
+ def initialize(instances, properties, instance_group, default_network, deployment_name, root_domain, dns_encoder)
12
+ @instances = instances
13
+ @properties = properties
14
+ @instance_group = instance_group
15
+ @default_network = default_network
16
+ @deployment_name = deployment_name
17
+ @root_domain = root_domain
18
+ @dns_encoder = dns_encoder
19
+ end
20
+
21
+ def p(*args)
22
+ names = Array(args[0])
23
+
24
+ names.each do |name|
25
+ result = lookup_property(@properties, name)
26
+ return result unless result.nil?
27
+ end
28
+
29
+ return args[1] if args.length == 2
30
+ raise UnknownProperty.new(names)
31
+ end
32
+
33
+ def if_p(*names)
34
+ values = names.map do |name|
35
+ value = lookup_property(@properties, name)
36
+ return Bosh::Template::EvaluationContext::ActiveElseBlock.new(self) if value.nil?
37
+ value
38
+ end
39
+
40
+ yield *values
41
+ Bosh::Template::EvaluationContext::InactiveElseBlock.new
42
+ end
43
+
44
+ def address(criteria = {})
45
+ raise NotImplementedError.new('link.address requires bosh director') if @dns_encoder.nil?
46
+
47
+ full_criteria = criteria.merge(
48
+ instance_group: @instance_group,
49
+ default_network: @default_network,
50
+ deployment_name: @deployment_name,
51
+ root_domain: @root_domain,
52
+ )
53
+
54
+ @dns_encoder.encode_query(full_criteria)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require 'bosh/template/property_helper'
2
+
3
+ module Bosh
4
+ module Template
5
+ class EvaluationLinkInstance
6
+ include PropertyHelper
7
+
8
+ attr_reader :name
9
+ attr_reader :index
10
+ attr_reader :id
11
+ attr_reader :az
12
+ attr_reader :address
13
+ attr_reader :properties
14
+ attr_reader :bootstrap
15
+
16
+ def initialize(name, index, id, az, address, properties, bootstrap)
17
+ @name = name
18
+ @index = index
19
+ @id = id
20
+ @az = az
21
+ @address = address
22
+ @properties = properties
23
+ @bootstrap = bootstrap
24
+ end
25
+
26
+ def p(*args)
27
+ names = Array(args[0])
28
+
29
+ names.each do |name|
30
+ result = lookup_property(@properties, name)
31
+ return result unless result.nil?
32
+ end
33
+
34
+ return args[1] if args.length == 2
35
+ raise UnknownProperty.new(names)
36
+ end
37
+
38
+ def if_p(*names)
39
+ values = names.map do |name|
40
+ value = lookup_property(@properties, name)
41
+ return ActiveElseBlock.new(self) if value.nil?
42
+ value
43
+ end
44
+
45
+ yield *values
46
+ InactiveElseBlock.new
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ module Bosh
2
+ module Template
3
+ class InvalidPropertyType < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module Bosh
2
+ module Template
3
+ class ManualLinkDnsEncoder
4
+ def initialize(manual_link_address)
5
+ @manual_link_address = manual_link_address
6
+ end
7
+
8
+ def encode_query(_)
9
+ @manual_link_address
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ require 'bosh/template/invalid_property_type'
2
+
3
+ module Bosh
4
+ module Template
5
+ module PropertyHelper
6
+ # Copies property with a given name from src to dst.
7
+ # @param [Hash] dst Property destination
8
+ # @param [Hash] src Property source
9
+ # @param [String] name Property name (dot-separated)
10
+ # @param [Object] default Default value (if property is not in src)
11
+ def copy_property(dst, src, name, default = nil)
12
+ keys = name.split(".")
13
+ src_ref = src
14
+ dst_ref = dst
15
+
16
+ keys.each do |key|
17
+ unless src_ref.is_a?(Hash)
18
+ raise Bosh::Template::InvalidPropertyType,
19
+ "Property '#{name}' expects a hash, but received '#{src_ref.class}'"
20
+ end
21
+ src_ref = src_ref[key]
22
+ break if src_ref.nil? # no property with this name is src
23
+ end
24
+
25
+ keys[0..-2].each do |key|
26
+ dst_ref[key] ||= {}
27
+ dst_ref = dst_ref[key]
28
+ end
29
+
30
+ dst_ref[keys[-1]] ||= {}
31
+ dst_ref[keys[-1]] = src_ref.nil? ? default : src_ref
32
+ end
33
+
34
+ # @param [Hash] collection Property collection
35
+ # @param [String] name Dot-separated property name
36
+ def lookup_property(collection, name)
37
+ return nil if collection.nil?
38
+ keys = name.split(".")
39
+ ref = collection
40
+
41
+ keys.each do |key|
42
+ ref = ref[key]
43
+ return nil if ref.nil?
44
+ end
45
+
46
+ ref
47
+ end
48
+
49
+ def sort_property(property)
50
+ if property.is_a?(Hash)
51
+ property.each do |k, v|
52
+ property[k] = sort_property(v)
53
+ end.sort.to_h
54
+ else
55
+ property
56
+ end
57
+ end
58
+
59
+ # Inject property with a given name and value to dst.
60
+ # @param [Hash] dst Property destination
61
+ # @param [String] name Property name (dot-separated)
62
+ # @param [Object] value Property value to be set
63
+ def set_property(dst, name, value)
64
+ keys = name.split('.')
65
+ dst_ref = dst
66
+
67
+ keys[0..-2].each do |key|
68
+ dst_ref[key] ||= {}
69
+ dst_ref = dst_ref[key]
70
+ end
71
+
72
+ dst_ref[keys[-1]] = value
73
+ end
74
+
75
+ def validate_properties_format(properties, name)
76
+ keys = name.split('.')
77
+ properties_ref = properties
78
+
79
+ keys.each do |key|
80
+ unless properties_ref.is_a?(Hash)
81
+ raise Bosh::Template::InvalidPropertyType,
82
+ "Property '#{name}' expects a hash, but received '#{properties_ref.class}'"
83
+ end
84
+ properties_ref = properties_ref[key]
85
+ break if properties_ref.nil? # no property with this name is src
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ require 'erb'
2
+ require 'json'
3
+ require 'bosh/template/evaluation_context'
4
+
5
+ module Bosh
6
+ module Template
7
+ class Renderer
8
+ def initialize(options={})
9
+ @context = options.fetch(:context)
10
+ end
11
+
12
+ def render(template_name)
13
+ spec = JSON.parse(@context)
14
+ evaluation_context = EvaluationContext.new(spec, nil)
15
+ template = ERB.new(File.read(template_name), safe_level = nil, trim_mode = "-")
16
+ template.result(evaluation_context.get_binding)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module Bosh
2
+ module Template
3
+ module Test
4
+
5
+ end
6
+ end
7
+ end
8
+
9
+ require 'bosh/template/test/job'
10
+ require 'bosh/template/test/link'
11
+ require 'bosh/template/test/link_instance'
12
+ require 'bosh/template/test/release_dir'
13
+ require 'bosh/template/test/instance_spec'
14
+ require 'bosh/template/test/template'
@@ -0,0 +1,40 @@
1
+ module Bosh::Template::Test
2
+ class InstanceSpec
3
+ def initialize(
4
+ address: 'my.bosh.com',
5
+ az: 'az1',
6
+ bootstrap: false,
7
+ deployment: 'my-deployment',
8
+ id: 'xxxxxx-xxxxxxxx-xxxxx',
9
+ index: 0,
10
+ ip: '192.168.0.0',
11
+ name: 'me',
12
+ networks: {'network1' => {'foo' => 'bar', 'ip' => '192.168.0.0'}}
13
+ )
14
+ @address = address
15
+ @az = az
16
+ @bootstrap = bootstrap
17
+ @deployment = deployment
18
+ @id = id
19
+ @index = index
20
+ @ip = ip
21
+ @name = name
22
+ @networks = networks
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ 'address' => @address,
28
+ 'az' => @az,
29
+ 'bootstrap' => @bootstrap,
30
+ 'deployment' => @deployment,
31
+ 'id' => @id,
32
+ 'index' => @index,
33
+ 'ip' => @ip,
34
+ 'name' => @name,
35
+ 'networks' => @networks,
36
+ 'job' => {'name' => @name}
37
+ }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ module Bosh::Template::Test
2
+ class Job
3
+ def initialize(release_path, name)
4
+ @release_path = release_path
5
+ @name = name
6
+ @job_path = File.join(@release_path, 'jobs', @name)
7
+ # raise "No such job at path: #{@job_path}" if !File.exist?(@job_path)
8
+ spec_path = File.join(@job_path, 'spec')
9
+ @spec = YAML.load(File.read(spec_path))
10
+ @templates = @spec['templates']
11
+ end
12
+
13
+ def template(rendered_file_name)
14
+ @templates.each_pair do |k, v|
15
+ return Template.new(@spec, File.join(@job_path, 'templates', k)) if v == rendered_file_name
16
+ end
17
+ raise "Template for rendered path filename not found: #{rendered_file_name}. Possible values are: [#{@templates.values.join(', ')}]"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Bosh::Template::Test
2
+ class Link
3
+ attr_reader :instances, :name, :properties
4
+
5
+ def initialize(name:, instances: [], properties: {})
6
+ @instances = instances
7
+ @name = name
8
+ @properties = properties
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ 'instances' => instances.map(&:to_h),
14
+ 'name' => name,
15
+ 'properties' => properties,
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ module Bosh::Template::Test
2
+ class LinkInstance
3
+ attr_reader :name, :id, :index, :az, :address, :bootstrap
4
+
5
+ def initialize(
6
+ name: 'i-name',
7
+ id: 'jkl8098',
8
+ index: 4,
9
+ az: 'az4',
10
+ address: 'link.instance.address.com',
11
+ bootstrap: false)
12
+ @bootstrap = bootstrap
13
+ @address = address
14
+ @az = az
15
+ @index = index
16
+ @id = id
17
+ @name = name
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ 'name' => name,
23
+ 'id' => id,
24
+ 'index' => index,
25
+ 'az' => az,
26
+ 'address' => address,
27
+ 'bootstrap' => bootstrap
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module Bosh::Template::Test
2
+ class ReleaseDir
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def job(name)
8
+ Bosh::Template::Test::Job.new(@path, name)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,77 @@
1
+ require 'bosh/template/evaluation_context'
2
+ require 'bosh/template/evaluation_context'
3
+
4
+ module Bosh::Template
5
+ module Test
6
+ class Template
7
+ include PropertyHelper
8
+
9
+ def initialize(job_spec_hash, template_path)
10
+ @job_spec_hash = job_spec_hash
11
+ @template_path = template_path
12
+ end
13
+
14
+ def render(manifest_properties_hash, spec: InstanceSpec.new, consumes: [])
15
+ spec_hash = {}
16
+ spec_hash['properties'] = hash_with_defaults(manifest_properties_hash)
17
+ sanitized_hash_with_spec = spec_hash.merge(spec.to_h)
18
+ sanitized_hash_with_spec['links'] = links_hash(consumes)
19
+
20
+ binding = Bosh::Template::EvaluationContext.new(sanitized_hash_with_spec, nil).get_binding
21
+ raise "No such file at #{@template_path}" unless File.exist?(@template_path)
22
+ ERB.new(File.read(@template_path)).result(binding)
23
+ end
24
+
25
+ private
26
+
27
+ def hash_with_defaults(manifest_properties_hash)
28
+ hash_properties = {}
29
+ spec_properties = @job_spec_hash['properties']
30
+
31
+ spec_properties.each_pair do |dotted_spec_key, property_def|
32
+ property_val = lookup_property(manifest_properties_hash, dotted_spec_key)
33
+ if property_val.nil? && !property_def['default'].nil?
34
+ property_val = property_def['default']
35
+ end
36
+ insert_property(hash_properties, dotted_spec_key, property_val)
37
+ end
38
+
39
+ hash_properties
40
+ end
41
+
42
+ def links_hash(links)
43
+ links_hash = {}
44
+ known_links = []
45
+
46
+ consumes = @job_spec_hash.fetch('consumes', [])
47
+ consumes.each do |consume|
48
+ known_links << consume['name']
49
+ link = links.find {|l| l.name == consume['name']}
50
+ links_hash[link.name] = link.to_h unless link.nil?
51
+ end
52
+
53
+ links.each do |link|
54
+ unless known_links.include?(link.name)
55
+ raise "Link '#{link.name}' is not declared as a consumed link in this job."
56
+ end
57
+ end
58
+
59
+ links_hash
60
+ end
61
+
62
+ def insert_property(nested_hash, dotted_key, value)
63
+ property_segments = dotted_key.split('.')
64
+ current_level = nested_hash
65
+
66
+ property_segments.each_with_index do |property_segment, i|
67
+ if i == property_segments.count - 1
68
+ current_level[property_segment] = value
69
+ else
70
+ current_level[property_segment] ||= {}
71
+ current_level = current_level[property_segment]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ module Bosh
2
+ module Template
3
+ class UnknownLink < StandardError
4
+ def initialize(name)
5
+ super("Can't find link '#{name}'")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Bosh
2
+ module Template
3
+ class UnknownProperty < StandardError
4
+ attr_reader :name
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ super("Can't find property '#{name}'")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Bosh
2
+ module Template
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bosh-template
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pivotal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: semi_semantic
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.0
27
+ description: Renders bosh templates
28
+ email: support@cloudfoundry.com
29
+ executables:
30
+ - bosh-template
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - bin/bosh-template
36
+ - lib/bosh/template.rb
37
+ - lib/bosh/template/evaluation_context.rb
38
+ - lib/bosh/template/evaluation_failed.rb
39
+ - lib/bosh/template/evaluation_link.rb
40
+ - lib/bosh/template/evaluation_link_instance.rb
41
+ - lib/bosh/template/invalid_property_type.rb
42
+ - lib/bosh/template/manual_link_dns_encoder.rb
43
+ - lib/bosh/template/property_helper.rb
44
+ - lib/bosh/template/renderer.rb
45
+ - lib/bosh/template/test.rb
46
+ - lib/bosh/template/test/instance_spec.rb
47
+ - lib/bosh/template/test/job.rb
48
+ - lib/bosh/template/test/link.rb
49
+ - lib/bosh/template/test/link_instance.rb
50
+ - lib/bosh/template/test/release_dir.rb
51
+ - lib/bosh/template/test/template.rb
52
+ - lib/bosh/template/unknown_link.rb
53
+ - lib/bosh/template/unknown_property.rb
54
+ - lib/bosh/template/version.rb
55
+ homepage: https://github.com/cloudfoundry/bosh
56
+ licenses:
57
+ - Apache-2.0
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.9.3
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.5.1
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Renders bosh templates
79
+ test_files: []