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 +7 -0
- data/README.md +39 -0
- data/bin/bosh-template +24 -0
- data/lib/bosh/template.rb +7 -0
- data/lib/bosh/template/evaluation_context.rb +218 -0
- data/lib/bosh/template/evaluation_failed.rb +6 -0
- data/lib/bosh/template/evaluation_link.rb +58 -0
- data/lib/bosh/template/evaluation_link_instance.rb +50 -0
- data/lib/bosh/template/invalid_property_type.rb +6 -0
- data/lib/bosh/template/manual_link_dns_encoder.rb +13 -0
- data/lib/bosh/template/property_helper.rb +90 -0
- data/lib/bosh/template/renderer.rb +20 -0
- data/lib/bosh/template/test.rb +14 -0
- data/lib/bosh/template/test/instance_spec.rb +40 -0
- data/lib/bosh/template/test/job.rb +20 -0
- data/lib/bosh/template/test/link.rb +19 -0
- data/lib/bosh/template/test/link_instance.rb +31 -0
- data/lib/bosh/template/test/release_dir.rb +11 -0
- data/lib/bosh/template/test/template.rb +77 -0
- data/lib/bosh/template/unknown_link.rb +9 -0
- data/lib/bosh/template/unknown_property.rb +12 -0
- data/lib/bosh/template/version.rb +5 -0
- metadata +79 -0
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,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,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,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,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
|
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: []
|