|
| 1 | +module VCAP::CloudController |
| 2 | + module Diego |
| 3 | + class ServiceBindingFilesBuilder |
| 4 | + class IncompatibleBindings < StandardError; end |
| 5 | + |
| 6 | + MAX_ALLOWED_BYTESIZE = 1_000_000 |
| 7 | + |
| 8 | + def self.build(app_or_process) |
| 9 | + new(app_or_process).build |
| 10 | + end |
| 11 | + |
| 12 | + def initialize(app_or_process) |
| 13 | + @app_or_process = app_or_process |
| 14 | + @service_binding_k8s_enabled = app_or_process.service_binding_k8s_enabled |
| 15 | + @file_based_vcap_services = app_or_process.file_based_vcap_services_enabled |
| 16 | + @service_bindings = app_or_process.service_bindings |
| 17 | + end |
| 18 | + |
| 19 | + def build |
| 20 | + if @service_binding_k8s_enabled |
| 21 | + build_service_binding_k8s |
| 22 | + elsif @file_based_vcap_services |
| 23 | + vcap_services = SystemEnvPresenter.new(@app_or_process).vcap_services[:VCAP_SERVICES] |
| 24 | + build_vcap_service_file(vcap_services) |
| 25 | + end |
| 26 | + end |
| 27 | + |
| 28 | + private |
| 29 | + |
| 30 | + def build_service_binding_k8s |
| 31 | + return nil unless @service_binding_k8s_enabled |
| 32 | + |
| 33 | + service_binding_files = {} |
| 34 | + names = Set.new # to check for duplicate binding names |
| 35 | + total_bytesize = 0 # to check the total bytesize |
| 36 | + |
| 37 | + @service_bindings.select(&:create_succeeded?).each do |service_binding| |
| 38 | + sb_hash = ServiceBindingPresenter.new(service_binding, include_instance: true).to_hash |
| 39 | + name = sb_hash[:name] |
| 40 | + raise IncompatibleBindings.new("Invalid binding name: '#{name}'. Name must match #{binding_naming_convention.inspect}") unless valid_name?(name) |
| 41 | + raise IncompatibleBindings.new("Duplicate binding name: #{name}") if names.add?(name).nil? |
| 42 | + |
| 43 | + # add the credentials first |
| 44 | + sb_hash.delete(:credentials)&.each { |k, v| total_bytesize += add_file(service_binding_files, name, k.to_s, v) } |
| 45 | + |
| 46 | + # add the rest of the hash; already existing credential keys are overwritten |
| 47 | + # VCAP_SERVICES attribute names are transformed (e.g. binding_guid -> binding-guid) |
| 48 | + sb_hash.each { |k, v| total_bytesize += add_file(service_binding_files, name, transform_vcap_services_attribute(k.to_s), v) } |
| 49 | + |
| 50 | + # add the type and provider |
| 51 | + label = sb_hash[:label] |
| 52 | + total_bytesize += add_file(service_binding_files, name, 'type', label) |
| 53 | + total_bytesize += add_file(service_binding_files, name, 'provider', label) |
| 54 | + end |
| 55 | + |
| 56 | + raise IncompatibleBindings.new("Bindings exceed the maximum allowed bytesize of #{MAX_ALLOWED_BYTESIZE}: #{total_bytesize}") if total_bytesize > MAX_ALLOWED_BYTESIZE |
| 57 | + |
| 58 | + service_binding_files.values |
| 59 | + end |
| 60 | + |
| 61 | + def build_vcap_service_file(vcap_services) |
| 62 | + path = 'vcap_services' |
| 63 | + vcap_services_string = Oj.dump(vcap_services, mode: :compat) |
| 64 | + total_bytesize = vcap_services_string.bytesize + path.bytesize |
| 65 | + |
| 66 | + raise IncompatibleBindings.new("Bindings exceed the maximum allowed bytesize of #{MAX_ALLOWED_BYTESIZE}: #{total_bytesize}") if total_bytesize > MAX_ALLOWED_BYTESIZE |
| 67 | + |
| 68 | + [::Diego::Bbs::Models::File.new(path: path, content: vcap_services_string)] |
| 69 | + end |
| 70 | + |
| 71 | + def binding_naming_convention |
| 72 | + /^[a-z0-9\-.]{1,253}$/ |
| 73 | + end |
| 74 | + |
| 75 | + # - adds a Diego::Bbs::Models::File object to the service_binding_files hash |
| 76 | + # - binding name is used as the directory name, key is used as the file name |
| 77 | + # - returns the bytesize of the path and content |
| 78 | + # - skips (and returns 0) if the value is nil or an empty array or hash |
| 79 | + # - serializes the value to JSON if it is a non-string object |
| 80 | + def add_file(service_binding_files, name, key, value) |
| 81 | + raise IncompatibleBindings.new("Invalid file name: #{key}") unless valid_name?(key) |
| 82 | + |
| 83 | + path = "#{name}/#{key}" |
| 84 | + content = if value.nil? |
| 85 | + return 0 |
| 86 | + elsif value.is_a?(String) |
| 87 | + value |
| 88 | + else |
| 89 | + return 0 if (value.is_a?(Array) || value.is_a?(Hash)) && value.empty? |
| 90 | + |
| 91 | + Oj.dump(value, mode: :compat) |
| 92 | + end |
| 93 | + |
| 94 | + service_binding_files[path] = ::Diego::Bbs::Models::File.new(path:, content:) |
| 95 | + path.bytesize + content.bytesize |
| 96 | + end |
| 97 | + |
| 98 | + def valid_name?(name) |
| 99 | + name.match?(binding_naming_convention) |
| 100 | + end |
| 101 | + |
| 102 | + def transform_vcap_services_attribute(name) |
| 103 | + if %w[binding_guid binding_name instance_guid instance_name syslog_drain_url volume_mounts].include?(name) |
| 104 | + name.tr('_', '-') |
| 105 | + else |
| 106 | + name |
| 107 | + end |
| 108 | + end |
| 109 | + end |
| 110 | + end |
| 111 | +end |
0 commit comments