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.
131 132 133 134 135 136 137 138 139 |
# File 'lib/chef_zero/server.rb', line 131 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)
142 143 144 |
# File 'lib/chef_zero/server.rb', line 142 def @options end |
#server ⇒ WEBrick::HTTPServer (readonly)
157 158 159 |
# File 'lib/chef_zero/server.rb', line 157 def server @server end |
Instance Method Details
#clear_data ⇒ Object
522 523 524 |
# File 'lib/chef_zero/server.rb', line 522 def clear_data data_store.clear end |
#data_store ⇒ ChefZero::DataStore
The data store for this server (default is in-memory).
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/chef_zero/server.rb', line 190 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
377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/chef_zero/server.rb', line 377 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.
217 218 219 |
# File 'lib/chef_zero/server.rb', line 217 def generate_real_keys? !!@options[:generate_real_keys] end |
#handle_socketless_request(request_env) ⇒ Object
338 339 340 |
# File 'lib/chef_zero/server.rb', line 338 def handle_socketless_request(request_env) app.call(request_env) end |
#inspect ⇒ Object
534 535 536 |
# File 'lib/chef_zero/server.rb', line 534 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.
273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/chef_zero/server.rb', line 273 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"'
}
}
}
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 520 |
# File 'lib/chef_zero/server.rb', line 420 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
179 180 181 182 183 |
# File 'lib/chef_zero/server.rb', line 179 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
389 390 391 |
# File 'lib/chef_zero/server.rb', line 389 def on_request(&block) @on_request_proc = block end |
#on_response(&block) ⇒ Object
393 394 395 |
# File 'lib/chef_zero/server.rb', line 393 def on_response(&block) @on_response_proc = block end |
#port ⇒ Integer
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/chef_zero/server.rb', line 145 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
526 527 528 |
# File 'lib/chef_zero/server.rb', line 526 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.
350 351 352 |
# File 'lib/chef_zero/server.rb', line 350 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.
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 261 |
# File 'lib/chef_zero/server.rb', line 231 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
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 332 |
# File 'lib/chef_zero/server.rb', line 285 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("/", Rackup::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 Thread.current.abort_on_exception = true @server.start ensure @port = nil @running = false 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
334 335 336 |
# File 'lib/chef_zero/server.rb', line 334 def start_socketless @port = SocketlessServerMap.instance.register_no_listen_server(self) end |
#stop(wait = 5) ⇒ Object
Gracefully stop the Chef Zero server.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/chef_zero/server.rb', line 361 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
530 531 532 |
# File 'lib/chef_zero/server.rb', line 530 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.
169 170 171 172 173 174 175 176 177 |
# File 'lib/chef_zero/server.rb', line 169 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 |