Skip to content

Commit 26e6766

Browse files
committed
ServiceBindingFilesBuilder
1 parent fe4b887 commit 26e6766

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class ServiceBindingFilesBuilder
2+
def self.build(app_or_process)
3+
new(app_or_process).build
4+
end
5+
6+
def initialize(app_or_process)
7+
@service_bindings = app_or_process.service_bindings
8+
end
9+
10+
def build
11+
service_binding_files = {}
12+
13+
@service_bindings.select(&:create_succeeded?).each do |service_binding|
14+
sb_hash = ServiceBindingPresenter.new(service_binding, include_instance: true).to_hash
15+
dir = sb_hash[:name]
16+
label = sb_hash[:label]
17+
18+
sb_hash.delete(:credentials)&.each { |k, v| add_file(service_binding_files, dir, k, v) }
19+
20+
sb_hash.each { |k, v| add_file(service_binding_files, dir, k, v) }
21+
22+
add_file(service_binding_files, dir, 'type', label)
23+
add_file(service_binding_files, dir, 'provider', label)
24+
end
25+
26+
service_binding_files.values
27+
end
28+
29+
private
30+
31+
def add_file(service_binding_files, dir, key, value)
32+
content = if value.nil?
33+
return
34+
elsif value.is_a?(String)
35+
value
36+
else
37+
return if (value.is_a?(Array) || value.is_a?(Hash)) && value.empty?
38+
39+
Oj.dump(value, mode: :compat)
40+
end
41+
42+
service_binding_files[key.to_sym] = ::Diego::Bbs::Models::Files.new(name: "#{dir}/#{key}", value: content)
43+
end
44+
end
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
require 'spec_helper'
2+
require 'cloud_controller/diego/service_binding_files_builder'
3+
4+
module VCAP::CloudController::Diego
5+
RSpec.shared_examples 'mapping of type and provider' do |label|
6+
it 'sets type and provider to the service label' do
7+
expect(service_binding_files.find { |f| f.name == "#{directory}/type" }).to have_attributes(value: label || 'service-name')
8+
expect(service_binding_files.find { |f| f.name == "#{directory}/provider" }).to have_attributes(value: label || 'service-name')
9+
expect(service_binding_files.find { |f| f.name == "#{directory}/label" }).to have_attributes(value: label || 'service-name')
10+
end
11+
end
12+
13+
RSpec.shared_examples 'mapping of binding metadata' do |name|
14+
it 'maps service binding metadata attributes to files' do
15+
expect(service_binding_files.find { |f| f.name == "#{directory}/binding_guid" }).to have_attributes(value: binding.guid)
16+
expect(service_binding_files.find { |f| f.name == "#{directory}/name" }).to have_attributes(value: name || 'binding-name')
17+
expect(service_binding_files.find { |f| f.name == "#{directory}/binding_name" }).to have_attributes(value: 'binding-name') if name.nil?
18+
end
19+
end
20+
21+
RSpec.shared_examples 'mapping of instance metadata' do |instance_name|
22+
it 'maps service instance metadata attributes to files' do
23+
expect(service_binding_files.find { |f| f.name == "#{directory}/instance_guid" }).to have_attributes(value: instance.guid)
24+
expect(service_binding_files.find { |f| f.name == "#{directory}/instance_name" }).to have_attributes(value: instance_name || 'instance-name')
25+
end
26+
end
27+
28+
RSpec.shared_examples 'mapping of plan metadata' do
29+
it 'maps service plan metadata attributes to files' do
30+
expect(service_binding_files.find { |f| f.name == "#{directory}/plan" }).to have_attributes(value: 'plan-name')
31+
end
32+
end
33+
34+
RSpec.shared_examples 'mapping of tags' do |tags|
35+
it 'maps (service tags merged with) instance tags to a file' do
36+
expect(service_binding_files.find do |f|
37+
f.name == "#{directory}/tags"
38+
end).to have_attributes(value: tags || '["a-service-tag","another-service-tag","an-instance-tag","another-instance-tag"]')
39+
end
40+
end
41+
42+
RSpec.shared_examples 'mapping of credentials' do |credential_files|
43+
it 'maps service binding credentials to individual files' do
44+
expected_credential_files = credential_files || {
45+
string: 'a string',
46+
number: '42',
47+
boolean: 'true',
48+
array: '["one","two","three"]',
49+
hash: '{"key":"value"}'
50+
}
51+
expected_credential_files.each do |name, content|
52+
expect(service_binding_files.find { |f| f.name == "#{directory}/#{name}" }).to have_attributes(value: content)
53+
end
54+
end
55+
end
56+
57+
RSpec.shared_examples 'expected files' do |files|
58+
it 'does not include other files' do
59+
other_files = service_binding_files.reject do |file|
60+
match = file.name.match(%r{^#{directory}/(.+)$})
61+
!match.nil? && !files.delete(match[1]).nil?
62+
end
63+
64+
expect(files).to be_empty
65+
expect(other_files).to be_empty
66+
end
67+
end
68+
69+
RSpec.describe ServiceBindingFilesBuilder do
70+
let(:service) { VCAP::CloudController::Service.make(label: 'service-name', tags: %w[a-service-tag another-service-tag]) }
71+
let(:plan) { VCAP::CloudController::ServicePlan.make(name: 'plan-name', service: service) }
72+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(name: 'instance-name', tags: %w[an-instance-tag another-instance-tag], service_plan: plan) }
73+
let(:binding_name) { 'binding-name' }
74+
let(:credentials) do
75+
{
76+
string: 'a string',
77+
number: 42,
78+
boolean: true,
79+
array: %w[one two three],
80+
hash: {
81+
key: 'value'
82+
}
83+
}
84+
end
85+
let(:syslog_drain_url) { nil }
86+
let(:volume_mounts) { nil }
87+
let(:binding) do
88+
VCAP::CloudController::ServiceBinding.make(
89+
name: binding_name,
90+
credentials: credentials,
91+
service_instance: instance,
92+
syslog_drain_url: syslog_drain_url,
93+
volume_mounts: volume_mounts
94+
)
95+
end
96+
let(:app) { binding.app }
97+
let(:directory) { 'binding-name' }
98+
99+
describe '#build' do
100+
subject(:build) { ServiceBindingFilesBuilder.build(app) }
101+
102+
it 'returns an array of Diego::Bbs::Models::Files' do
103+
expect(build).to be_an(Array)
104+
expect(build).not_to be_empty
105+
expect(build).to all(be_a(Diego::Bbs::Models::Files))
106+
end
107+
108+
describe 'mapping rules for service binding files' do
109+
subject(:service_binding_files) { build }
110+
111+
it 'puts all files into a directory named after the service binding' do
112+
expect(service_binding_files).not_to be_empty
113+
expect(service_binding_files).to all(have_attributes(name: match(%r{^binding-name/.+$})))
114+
end
115+
116+
include_examples 'mapping of type and provider'
117+
include_examples 'mapping of binding metadata'
118+
include_examples 'mapping of instance metadata'
119+
include_examples 'mapping of plan metadata'
120+
include_examples 'mapping of tags'
121+
include_examples 'mapping of credentials'
122+
123+
it 'omits null or empty array attributes' do
124+
expect(service_binding_files).not_to include(have_attributes(name: 'binding-name/syslog_drain_url'))
125+
expect(service_binding_files).not_to include(have_attributes(name: 'binding-name/volume_mounts'))
126+
end
127+
128+
include_examples 'expected files', %w[type provider label binding_guid name binding_name instance_guid instance_name plan tags string number boolean array hash]
129+
130+
context 'when binding name is nil' do
131+
let(:binding_name) { nil }
132+
let(:directory) { 'instance-name' }
133+
134+
include_examples 'mapping of type and provider'
135+
include_examples 'mapping of binding metadata', 'instance-name'
136+
include_examples 'mapping of instance metadata'
137+
include_examples 'mapping of plan metadata'
138+
include_examples 'mapping of tags'
139+
include_examples 'mapping of credentials'
140+
141+
include_examples 'expected files', %w[type provider label binding_guid name instance_guid instance_name plan tags string number boolean array hash]
142+
end
143+
144+
context 'when syslog_drain_url is set' do
145+
let(:syslog_drain_url) { 'https://syslog.drain' }
146+
147+
it 'maps the attribute to a file' do
148+
expect(service_binding_files.find { |f| f.name == 'binding-name/syslog_drain_url' }).to have_attributes(value: 'https://syslog.drain')
149+
end
150+
151+
include_examples 'mapping of type and provider'
152+
include_examples 'mapping of binding metadata'
153+
include_examples 'mapping of instance metadata'
154+
include_examples 'mapping of plan metadata'
155+
include_examples 'mapping of tags'
156+
include_examples 'mapping of credentials'
157+
158+
include_examples 'expected files',
159+
%w[type provider label binding_guid name binding_name instance_guid instance_name plan tags string number boolean array hash syslog_drain_url]
160+
end
161+
162+
context 'when volume_mounts is set' do
163+
let(:volume_mounts) do
164+
[{
165+
container_dir: 'dir1',
166+
device_type: 'type1',
167+
mode: 'mode1',
168+
foo: 'bar'
169+
}, {
170+
container_dir: 'dir2',
171+
device_type: 'type2',
172+
mode: 'mode2',
173+
foo: 'baz'
174+
}]
175+
end
176+
177+
it 'maps the attribute to a file' do
178+
expect(service_binding_files.find do |f|
179+
f.name == 'binding-name/volume_mounts'
180+
end).to have_attributes(value: '[{"container_dir":"dir1","device_type":"type1","mode":"mode1"},{"container_dir":"dir2","device_type":"type2","mode":"mode2"}]')
181+
end
182+
183+
include_examples 'mapping of type and provider'
184+
include_examples 'mapping of binding metadata'
185+
include_examples 'mapping of instance metadata'
186+
include_examples 'mapping of plan metadata'
187+
include_examples 'mapping of tags'
188+
include_examples 'mapping of credentials'
189+
190+
include_examples 'expected files',
191+
%w[type provider label binding_guid name binding_name instance_guid instance_name plan tags string number boolean array hash volume_mounts]
192+
end
193+
194+
context 'when the instance is user-provided' do
195+
let(:instance) { VCAP::CloudController::UserProvidedServiceInstance.make(name: 'upsi', tags: %w[an-upsi-tag another-upsi-tag]) }
196+
197+
include_examples 'mapping of type and provider', 'user-provided'
198+
include_examples 'mapping of binding metadata'
199+
include_examples 'mapping of instance metadata', 'upsi'
200+
include_examples 'mapping of tags', '["an-upsi-tag","another-upsi-tag"]'
201+
include_examples 'mapping of credentials'
202+
203+
include_examples 'expected files', %w[type provider label binding_guid name binding_name instance_guid instance_name tags string number boolean array hash]
204+
end
205+
206+
context 'when there are duplicate keys at different levels' do
207+
let(:credentials) { { type: 'duplicate-type', name: 'duplicate-name', credentials: { password: 'secret' } } }
208+
209+
include_examples 'mapping of type and provider' # no 'duplicate-type'
210+
include_examples 'mapping of binding metadata' # no 'duplicate-name'
211+
include_examples 'mapping of instance metadata'
212+
include_examples 'mapping of plan metadata'
213+
include_examples 'mapping of tags'
214+
include_examples 'mapping of credentials', { credentials: '{"password":"secret"}' }
215+
216+
include_examples 'expected files', %w[type provider label binding_guid name binding_name instance_guid instance_name plan tags credentials]
217+
end
218+
end
219+
end
220+
end
221+
end

0 commit comments

Comments
 (0)