bosh-template 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|