Class: ChefZero::Server
- Inherits:
-
Object
- Object
- ChefZero::Server
- Includes:
- Endpoints
- Defined in:
- lib/chef_zero/server.rb
Constant Summary collapse
- DEFAULT_OPTIONS =
{ host: ["127.0.0.1"], port: 8889, log_level: :warn, generate_real_keys: true, single_org: "chef", ssl: false, }.freeze
- GLOBAL_ENDPOINTS =
[ "/license", "/version", "/server_api_version", ].freeze
Instance Attribute Summary collapse
- #options ⇒ Hash readonly
- #server ⇒ WEBrick::HTTPServer readonly
Instance Method Summary collapse
- #clear_data ⇒ Object
-
#data_store ⇒ ChefZero::DataStore
The data store for this server (default is in-memory).
- #gen_key_pair ⇒ Object
-
#generate_real_keys? ⇒ Boolean
Boolean method to determine if real Public/Private keys should be generated.
- #handle_socketless_request(request_env) ⇒ Object
-
#initialize(options = {}) ⇒ Server
constructor
A new instance of Server.
- #inspect ⇒ Object
-
#listen(hosts, port) ⇒ Thread
Start a Chef Zero server in a forked process.
-
#load_data(contents, org_name = nil) ⇒ Object
Load data in a nice, friendly form: { ‘roles’ => { ‘desert’ => ‘{ “description”: “Hot and dry”’ }, ‘rainforest’ => { “description” => ‘Wet and humid’ } }, ‘cookbooks’ => { ‘apache2-1.0.1’ => { ‘templates’ => { ‘default’ => { ‘blah.txt’ => ‘hi’ }} ‘recipes’ => { ‘default.rb’ => ‘template “blah.txt”’ } ‘metadata.rb’ => ‘depends “mysql”’ }, ‘apache2-1.2.0’ => { ‘templates’ => { ‘default’ => { ‘blah.txt’ => ‘lo’ }} ‘recipes’ => { ‘default.rb’ => ‘template “blah.txt”’ } ‘metadata.rb’ => ‘depends “mysql”’ }, ‘mysql’ => { ‘recipes’ => { ‘default.rb’ => ‘file { contents “hi” }’ }, ‘metadata.rb’ => ‘version “1.0.0”’ } } }.
- #local_mode_url ⇒ Object
- #on_request(&block) ⇒ Object
- #on_response(&block) ⇒ Object
- #port ⇒ Integer
- #request_handler(&block) ⇒ Object
-
#running? ⇒ Boolean
Boolean method to determine if the server is currently ready to accept requests.
-
#start(publish = true) ⇒ nil
Start a Chef Zero server in the current thread.
- #start_background(wait = 5) ⇒ Object
- #start_socketless ⇒ Object
-
#stop(wait = 5) ⇒ Object
Gracefully stop the Chef Zero server.
- #to_s ⇒ Object
-
#url ⇒ String
The URL for this Chef Zero server.
Constructor Details
#initialize(options = {}) ⇒ Server
Returns a new instance of Server.
130 131 132 133 134 135 136 137 138 |
# File 'lib/chef_zero/server.rb', line 130 def initialize( = {}) @options = DEFAULT_OPTIONS.merge() if @options[:single_org] && !@options.key?(:osc_compat) @options[:osc_compat] = true end @options.freeze ChefZero::Log.level = @options[:log_level].to_sym @app = nil end |
Instance Attribute Details
#options ⇒ Hash (readonly)
141 142 143 |
# File 'lib/chef_zero/server.rb', line 141 def @options end |
#server ⇒ WEBrick::HTTPServer (readonly)
156 157 158 |
# File 'lib/chef_zero/server.rb', line 156 def server @server end |
Instance Method Details
#clear_data ⇒ Object
521 522 523 |
# File 'lib/chef_zero/server.rb', line 521 def clear_data data_store.clear end |
#data_store ⇒ ChefZero::DataStore
The data store for this server (default is in-memory).
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/chef_zero/server.rb', line 189 def data_store @data_store ||= begin result = @options[:data_store] || DataStore::DefaultFacade.new(DataStore::MemoryStoreV2.new, [:single_org], [:osc_compat]) if [:single_org] if !result.respond_to?(:interface_version) || result.interface_version == 1 result = ChefZero::DataStore::V1ToV2Adapter.new(result, [:single_org]) result = ChefZero::DataStore::DefaultFacade.new(result, [:single_org], [:osc_compat]) end else if !result.respond_to?(:interface_version) || result.interface_version == 1 raise "Multi-org not supported by data store #{result}!" end end result end end |
#gen_key_pair ⇒ Object
376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/chef_zero/server.rb', line 376 def gen_key_pair if generate_real_keys? private_key = OpenSSL::PKey::RSA.new(2048) public_key = private_key.public_key.to_s public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, "-----BEGIN PUBLIC KEY-----") public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1') [private_key.to_s, public_key] else [PRIVATE_KEY, PUBLIC_KEY] end end |
#generate_real_keys? ⇒ Boolean
Boolean method to determine if real Public/Private keys should be generated.
216 217 218 |
# File 'lib/chef_zero/server.rb', line 216 def generate_real_keys? !!@options[:generate_real_keys] end |
#handle_socketless_request(request_env) ⇒ Object
337 338 339 |
# File 'lib/chef_zero/server.rb', line 337 def handle_socketless_request(request_env) app.call(request_env) end |
#inspect ⇒ Object
533 534 535 |
# File 'lib/chef_zero/server.rb', line 533 def inspect "#<#{self.class} @url=#{url.inspect}>" end |
#listen(hosts, port) ⇒ Thread
Start a Chef Zero server in a forked process. This method returns the PID to the forked process.
272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/chef_zero/server.rb', line 272 def listen(hosts, port) hosts.each do |host| @server.listen(host, port) end true rescue Errno::EADDRINUSE ChefZero::Log.warn("Port #{port} not available") @server.listeners.each(&:close) @server.listeners.clear false end |
#load_data(contents, org_name = nil) ⇒ Object
Load data in a nice, friendly form: {
'roles' => {
'desert' => '{ "description": "Hot and dry"' },
'rainforest' => { "description" => 'Wet and humid' }
},
'cookbooks' => {
'apache2-1.0.1' => {
'templates' => { 'default' => { 'blah.txt' => 'hi' }}
'recipes' => { 'default.rb' => 'template "blah.txt"' }
'metadata.rb' => 'depends "mysql"'
},
'apache2-1.2.0' => {
'templates' => { 'default' => { 'blah.txt' => 'lo' }}
'recipes' => { 'default.rb' => 'template "blah.txt"' }
'metadata.rb' => 'depends "mysql"'
},
'mysql' => {
'recipes' => { 'default.rb' => 'file { contents "hi" }' },
'metadata.rb' => 'version "1.0.0"'
}
}
}
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/chef_zero/server.rb', line 419 def load_data(contents, org_name = nil) org_name ||= [:single_org] if org_name.nil? && contents.keys != [ "users" ] raise "Must pass an org name to load_data or run in single_org mode" end %w{clients containers environments groups nodes roles sandboxes}.each do |data_type| if contents[data_type] dejsonize_children(contents[data_type]).each_pair do |name, data| data_store.set(["organizations", org_name, data_type, name], data, :create) end end end if contents["users"] dejsonize_children(contents["users"]).each_pair do |name, data| if [:osc_compat] data_store.set(["organizations", org_name, "users", name], data, :create) else # Create the user and put them in the org data_store.set(["users", name], data, :create) if org_name data_store.set(["organizations", org_name, "users", name], "{}", :create) end end end end if contents["members"] contents["members"].each do |name| data_store.set(["organizations", org_name, "users", name], "{}", :create) end end if contents["invites"] contents["invites"].each do |name| data_store.set(["organizations", org_name, "association_requests", name], "{}", :create) end end if contents["acls"] dejsonize_children(contents["acls"]).each do |path, acl| path = [ "organizations", org_name ] + path.split("/") path = ChefData::AclPath.get_acl_data_path(path) ChefZero::RSpec.server.data_store.set(path, acl) end end if contents["data"] contents["data"].each_pair do |key, data_bag| data_store.create_dir(["organizations", org_name, "data"], key, :recursive) dejsonize_children(data_bag).each do |item_name, item| data_store.set(["organizations", org_name, "data", key, item_name], item, :create) end end end if contents["policies"] contents["policies"].each_pair do |policy_name, policy_struct| # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive) dejsonize_children(policy_struct).each do |revision, policy_data| data_store.set(["organizations", org_name, "policies", policy_name, "revisions", revision], policy_data, :create, :create_dir) end end end if contents["policy_groups"] contents["policy_groups"].each_pair do |group_name, group| group["policies"].each do |policy_name, policy_revision| data_store.set(["organizations", org_name, "policy_groups", group_name, "policies", policy_name], FFI_Yajl::Encoder.encode(policy_revision["revision_id"], pretty: true), :create, :create_dir) end end end %w{cookbooks cookbook_artifacts}.each do |cookbook_type| if contents[cookbook_type] contents[cookbook_type].each_pair do |name_version, cookbook| if cookbook_type == "cookbook_artifacts" name, _, identifier = name_version.rpartition("-") cookbook_data = ChefData::CookbookData.to_hash(cookbook, name, identifier) elsif name_version =~ /(.+)-(\d+\.\d+\.\d+)$/ cookbook_data = ChefData::CookbookData.to_hash(cookbook, $1, $2) else cookbook_data = ChefData::CookbookData.to_hash(cookbook, name_version) end raise "No version specified" unless cookbook_data[:version] data_store.create_dir(["organizations", org_name, cookbook_type], cookbook_data[:cookbook_name], :recursive) data_store.set(["organizations", org_name, cookbook_type, cookbook_data[:cookbook_name], cookbook_data[:version]], FFI_Yajl::Encoder.encode(cookbook_data, pretty: true), :create) cookbook_data.values.each do |files| next unless files.is_a? Array files.each do |file| data_store.set(["organizations", org_name, "file_store", "checksums", file[:checksum]], get_file(cookbook, file[:path]), :create) end end end end end end |
#local_mode_url ⇒ Object
178 179 180 181 182 |
# File 'lib/chef_zero/server.rb', line 178 def local_mode_url raise "Port not yet set, cannot generate URL" unless port.is_a?(Integer) "chefzero://localhost:#{port}" end |
#on_request(&block) ⇒ Object
388 389 390 |
# File 'lib/chef_zero/server.rb', line 388 def on_request(&block) @on_request_proc = block end |
#on_response(&block) ⇒ Object
392 393 394 |
# File 'lib/chef_zero/server.rb', line 392 def on_response(&block) @on_response_proc = block end |
#port ⇒ Integer
144 145 146 147 148 149 150 151 152 153 |
# File 'lib/chef_zero/server.rb', line 144 def port if @port @port # If options[:port] is not an Array or an Enumerable, it is just an Integer. elsif ![:port].respond_to?(:each) [:port] else raise "port cannot be determined until server is started" end end |
#request_handler(&block) ⇒ Object
525 526 527 |
# File 'lib/chef_zero/server.rb', line 525 def request_handler(&block) @request_handler = block end |
#running? ⇒ Boolean
Boolean method to determine if the server is currently ready to accept requests. This method will attempt to make an HTTP request against the server. If this method returns true, you are safe to make a request.
349 350 351 |
# File 'lib/chef_zero/server.rb', line 349 def running? !@server.nil? && @running && @server.status == :Running end |
#start(publish = true) ⇒ nil
Start a Chef Zero server in the current thread. You can stop this server by canceling the current thread.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/chef_zero/server.rb', line 230 def start(publish = true) publish = publish[:publish] if publish.is_a?(Hash) # Legacy API if publish output = publish.respond_to?(:puts) ? publish : STDOUT output.puts <<-EOH.gsub(/^ {10}/, "") >> Starting #{ChefZero::Dist::PRODUCT} (v#{ChefZero::VERSION})... EOH end thread = start_background if publish output = publish.respond_to?(:puts) ? publish : STDOUT output.puts <<-EOH.gsub(/^ {10}/, "") >> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url} >> Press CTRL+C to stop EOH end %w{INT TERM}.each do |signal| Signal.trap(signal) do puts "\n>> Stopping #{ChefZero::Dist::PRODUCT}..." @server.shutdown end end # Move the background process to the main thread thread.join end |
#start_background(wait = 5) ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/chef_zero/server.rb', line 284 def start_background(wait = 5) @server = WEBrick::HTTPServer.new( DoNotListen: true, AccessLog: [], Logger: WEBrick::Log.new(StringIO.new, 7), RequestTimeout: 300, SSLEnable: [:ssl], SSLOptions: ssl_opts, SSLCertName: [ [ "CN", WEBrick::Utils.getservername ] ], StartCallback: proc do @running = true end ) ENV["HTTPS"] = "on" if [:ssl] @server.mount("/", Rack::Handler::WEBrick, app) # Pick a port # If options[:port] can be an Enumerator, an Array, or an Integer, # we need something that can respond to .each (Enum and Array can already). Array([:port]).each do |port| if listen(Array([:host]), port) @port = port break end end unless @port raise Errno::EADDRINUSE, "No port in :port range #{[:port]} is available" end # Start the server in the background @thread = Thread.new do begin Thread.current.abort_on_exception = true @server.start ensure @port = nil @running = false end end # Do not return until the web server is genuinely started. sleep(0.01) while !@running && @thread.alive? SocketlessServerMap.instance.register_port(@port, self) @thread end |
#start_socketless ⇒ Object
333 334 335 |
# File 'lib/chef_zero/server.rb', line 333 def start_socketless @port = SocketlessServerMap.instance.register_no_listen_server(self) end |
#stop(wait = 5) ⇒ Object
Gracefully stop the Chef Zero server.
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/chef_zero/server.rb', line 360 def stop(wait = 5) if @running @server.shutdown if @server @thread.join(wait) if @thread end rescue Timeout::Error if @thread ChefZero::Log.error("#{ChefZero::Dist::PRODUCT} did not stop within #{wait} seconds! Killing...") @thread.kill SocketlessServerMap.deregister(port) end ensure @server = nil @thread = nil end |
#to_s ⇒ Object
529 530 531 |
# File 'lib/chef_zero/server.rb', line 529 def to_s "#<#{self.class} #{url}>" end |
#url ⇒ String
The URL for this Chef Zero server. If the given host is an IPV6 address, it is escaped in brackets according to RFC-2732.
168 169 170 171 172 173 174 175 176 |
# File 'lib/chef_zero/server.rb', line 168 def url sch = @options[:ssl] ? "https" : "http" hosts = Array(@options[:host]) @url ||= if hosts.first.include?(":") URI("#{sch}://[#{hosts.first}]:#{port}").to_s else URI("#{sch}://#{hosts.first}:#{port}").to_s end end |