github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/integration/assets/service_broker/service_broker.rb (about) 1 ENV['RACK_ENV'] ||= 'development' 2 3 require 'rubygems' 4 require 'sinatra/base' 5 require 'json' 6 require 'pp' 7 require 'logger' 8 require 'rainbow/ext/string' 9 10 require 'bundler' 11 Bundler.require :default, ENV['RACK_ENV'].to_sym 12 Rainbow.enabled = true 13 14 $stdout.sync = true 15 $stderr.sync = true 16 17 class ServiceInstance 18 attr_reader :provision_data, :fetch_count, :deleted 19 20 def initialize(opts={}) 21 @provision_data = opts.fetch(:provision_data) 22 @fetch_count = opts.fetch(:fetch_count, 0) 23 @deleted = opts.fetch(:deleted, false) 24 end 25 26 def plan_id 27 @provision_data['plan_id'] 28 end 29 30 def update!(updated_data) 31 @provision_data.merge!(updated_data) 32 @fetch_count = 0 33 self 34 end 35 36 def delete! 37 @deleted = true 38 @fetch_count = 0 39 self 40 end 41 42 def increment_fetch_count 43 @fetch_count += 1 44 end 45 46 def to_json(opts={}) 47 { 48 provision_data: provision_data, 49 fetch_count: fetch_count, 50 deleted: deleted 51 }.to_json(opts) 52 end 53 end 54 55 class DataSource 56 attr_reader :data 57 58 def initialize(data = nil) 59 @data = data || JSON.parse(File.read(File.absolute_path('data.json'))) 60 end 61 62 def max_fetch_service_instance_requests 63 @data['max_fetch_service_instance_requests'] || 1 64 end 65 66 def service_instance_by_id(cc_id) 67 @data['service_instances'][cc_id] 68 end 69 70 def create_service_instance(cc_id, json_data) 71 service_instance = ServiceInstance.new( 72 provision_data: json_data, 73 ) 74 75 @data['service_instances'][cc_id] = service_instance 76 77 service_instance 78 end 79 80 def create_service_binding(instance_id, binding_id, binding_data) 81 @data['service_instances'][binding_id] = { 82 'binding_data' => binding_data, 83 'instance_id' => instance_id, 84 } 85 end 86 87 def delete_service_binding(binding_id) 88 @data['service_instances'].delete(binding_id) 89 end 90 91 def merge!(data) 92 data = data.dup 93 data['service_instances'] = data.fetch('service_instances', {}).inject({}) do |service_instances, (guid, instance_data)| 94 symbolized_data = instance_data.inject({}) do |memo,(k,v)| 95 memo[k.to_sym] = v 96 memo 97 end 98 99 service_instances[guid] = ServiceInstance.new(symbolized_data) 100 service_instances 101 end 102 103 data.each_pair do |key, value| 104 if @data[key] && @data[key].is_a?(Hash) 105 @data[key].merge!(value) 106 else 107 @data[key] = value 108 end 109 end 110 end 111 112 def without_instances_or_bindings 113 @data.reject { |key| %w(service_instances service_bindings).include?(key) } 114 end 115 116 def behavior_for_type(type, plan_id) 117 plans_or_default_behavior = @data['behaviors'][type.to_s] 118 119 return plans_or_default_behavior if type == :catalog 120 121 raise "Behavior object is missing key: #{type} (tried to lookup plan_id #{plan_id})" unless plans_or_default_behavior 122 123 if plan_id && plans_or_default_behavior.has_key?(plan_id) 124 plans_or_default_behavior[plan_id] 125 else 126 $log.info("Could not find response for plan id: #{plan_id}") 127 return plans_or_default_behavior['default'] if plans_or_default_behavior['default'] 128 raise "Behavior for #{type} is missing response for plan_id #{plan_id} and default response." 129 end 130 end 131 end 132 133 class ServiceBroker < Sinatra::Base 134 set :logging, true 135 136 configure :production, :development, :test do 137 $datasource = DataSource.new 138 $log = Logger.new(STDOUT) 139 $log.level = Logger::INFO 140 $log.formatter = proc do |severity, datetime, progname, msg| 141 "#{severity}: #{msg}\n" 142 end 143 end 144 145 def log(request) 146 $log.info "#{request.env['REQUEST_METHOD']} #{request.env['PATH_INFO']} #{request.env['QUERY_STRING']}".color(:yellow) 147 request.body.rewind 148 headers = find_headers(request) 149 $log.info "Request headers: #{headers}".color(:cyan) 150 $log.info "Request body: #{request.body.read}".color(:yellow) 151 request.body.rewind 152 end 153 154 def find_headers(request) 155 request.env.select { |key, _| key =~ /HTTP/ } 156 end 157 158 def log_response(status, body) 159 $log.info "Response: status=#{status}, body=#{body}".color(:green) 160 body 161 end 162 163 def respond_with_behavior(behavior, accepts_incomplete=false) 164 sleep behavior['sleep_seconds'] 165 166 if behavior['async_only'] && !accepts_incomplete 167 respond_async_required 168 else 169 respond_from_config(behavior) 170 end 171 end 172 173 def respond_async_required 174 status 422 175 log_response(status, { 176 'error' => 'AsyncRequired', 177 'description' => 'This service plan requires client support for asynchronous service operations.' 178 }.to_json) 179 end 180 181 def respond_from_config(behavior) 182 status behavior['status'] 183 if behavior['body'] 184 log_response(status, behavior['body'].to_json) 185 else 186 log_response(status, behavior['raw_body']) 187 end 188 end 189 190 before do 191 log(request) 192 end 193 194 # fetch catalog 195 get '/v2/catalog/?' do 196 respond_with_behavior($datasource.behavior_for_type(:catalog, nil)) 197 end 198 199 # provision 200 put '/v2/service_instances/:id/?' do |id| 201 json_body = JSON.parse(request.body.read) 202 service_instance = $datasource.create_service_instance(id, json_body) 203 respond_with_behavior($datasource.behavior_for_type(:provision, service_instance.plan_id), params['accepts_incomplete']) 204 end 205 206 # fetch service instance 207 get '/v2/service_instances/:id/last_operation/?' do |id| 208 service_instance = $datasource.service_instance_by_id(id) 209 if service_instance 210 plan_id = service_instance.plan_id 211 212 if service_instance.increment_fetch_count > $datasource.max_fetch_service_instance_requests 213 state = 'finished' 214 else 215 state = 'in_progress' 216 end 217 218 behavior = $datasource.behavior_for_type('fetch', plan_id)[state] 219 sleep behavior['sleep_seconds'] 220 status behavior['status'] 221 222 if behavior['body'] 223 log_response(status, behavior['body'].to_json) 224 else 225 log_response(status, behavior['raw_body']) 226 end 227 else 228 status 200 229 log_response(status, { 230 state: 'failed', 231 description: "Broker could not find service instance by the given id #{id}", 232 }.to_json) 233 end 234 end 235 236 # update service instance 237 patch '/v2/service_instances/:id/?' do |id| 238 json_body = JSON.parse(request.body.read) 239 service_instance = $datasource.service_instance_by_id(id) 240 plan_id = json_body['plan_id'] 241 242 behavior = $datasource.behavior_for_type(:update, plan_id) 243 if [200, 202].include?(behavior['status']) 244 service_instance.update!(json_body) if service_instance 245 end 246 247 respond_with_behavior(behavior, params['accepts_incomplete']) 248 end 249 250 # deprovision 251 delete '/v2/service_instances/:id/?' do |id| 252 service_instance = $datasource.service_instance_by_id(id) 253 if service_instance 254 service_instance.delete! 255 respond_with_behavior($datasource.behavior_for_type(:deprovision, service_instance.plan_id), params[:accepts_incomplete]) 256 else 257 respond_with_behavior($datasource.behavior_for_type(:deprovision, nil), params[:accepts_incomplete]) 258 end 259 end 260 261 # create service binding 262 put '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id| 263 content_type :json 264 json_body = JSON.parse(request.body.read) 265 266 service_binding = $datasource.create_service_binding(instance_id, binding_id, json_body) 267 respond_with_behavior($datasource.behavior_for_type(:bind, service_binding['binding_data']['plan_id'])) 268 end 269 270 # delete service binding 271 delete '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id| 272 content_type :json 273 274 service_binding = $datasource.delete_service_binding(binding_id) 275 if service_binding 276 respond_with_behavior($datasource.behavior_for_type(:unbind, service_binding['binding_data']['plan_id'])) 277 else 278 respond_with_behavior($datasource.behavior_for_type(:unbind, nil)) 279 end 280 end 281 282 get '/config/all/?' do 283 log_response(status, JSON.pretty_generate($datasource.data)) 284 end 285 286 get '/config/?' do 287 log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings)) 288 end 289 290 post '/config/?' do 291 json_body = JSON.parse(request.body.read) 292 $datasource.merge!(json_body) 293 log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings)) 294 end 295 296 post '/config/reset/?' do 297 $datasource = DataSource.new 298 log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings)) 299 end 300 301 error do 302 status 500 303 e = env['sinatra.error'] 304 log_response(status, JSON.pretty_generate({ 305 error: true, 306 message: e.message, 307 path: request.url, 308 timestamp: Time.new, 309 type: '500', 310 backtrace: e.backtrace 311 })) 312 end 313 314 run! if app_file == $0 315 end