Class: ChefZero::Server

Inherits:
Object
  • Object
show all
Includes:
Endpoints
Defined in:
lib/chef_zero/server.rb

Constant Summary collapse

DEFAULT_OPTIONS =
{
  :host => '127.0.0.1',
  :port => 8889,
  :log_level => :info,
  :generate_real_keys => true,
  :single_org => 'chef',
  :ssl => false
}.freeze
GLOBAL_ENDPOINTS =
[
  '/license',
  '/version',
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Server

Returns a new instance of Server


106
107
108
109
110
111
112
113
114
# File 'lib/chef_zero/server.rb', line 106

def initialize(options = {})
  @options = DEFAULT_OPTIONS.merge(options)
  if @options[:single_org] && !@options.has_key?(:osc_compat)
    @options[:osc_compat] = true
  end
  @options.freeze
  ChefZero::Log.level = @options[:log_level].to_sym
  @app = nil
end

Instance Attribute Details

#optionsHash (readonly)

Returns:

  • (Hash)

117
118
119
# File 'lib/chef_zero/server.rb', line 117

def options
  @options
end

#serverWEBrick::HTTPServer (readonly)

Returns:

  • (WEBrick::HTTPServer)

131
132
133
# File 'lib/chef_zero/server.rb', line 131

def server
  @server
end

Instance Method Details

#clear_dataObject


463
464
465
# File 'lib/chef_zero/server.rb', line 463

def clear_data
  data_store.clear
end

#data_storeChefZero::DataStore

The data store for this server (default is in-memory).

Returns:


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/chef_zero/server.rb', line 163

def data_store
  @data_store ||= begin
    result = @options[:data_store] || DataStore::DefaultFacade.new(DataStore::MemoryStoreV2.new, options[:single_org], options[:osc_compat])
    if options[:single_org]

      if !result.respond_to?(:interface_version) || result.interface_version == 1
        result = ChefZero::DataStore::V1ToV2Adapter.new(result, options[:single_org])
        result = ChefZero::DataStore::DefaultFacade.new(result, options[:single_org], options[: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_pairObject


343
344
345
346
347
348
349
350
351
352
353
# File 'lib/chef_zero/server.rb', line 343

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.

Returns:

  • (Boolean)

    true if real keys should be created, false otherwise


190
191
192
# File 'lib/chef_zero/server.rb', line 190

def generate_real_keys?
  !!@options[:generate_real_keys]
end

#handle_socketless_request(request_env) ⇒ Object


304
305
306
# File 'lib/chef_zero/server.rb', line 304

def handle_socketless_request(request_env)
  app.call(request_env)
end

#inspectObject


475
476
477
# File 'lib/chef_zero/server.rb', line 475

def inspect
  "#<#{self.class} @url=#{url.inspect}>"
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"'
  }
}

}


386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
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
# File 'lib/chef_zero/server.rb', line 386

def load_data(contents, org_name = nil)
  org_name ||= options[: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 options[: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['cookbooks']
    contents['cookbooks'].each_pair do |name_version, cookbook|
      if 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" if !cookbook_data[:version]
      data_store.create_dir(['organizations', org_name, 'cookbooks'], cookbook_data[:cookbook_name], :recursive)
      data_store.set(['organizations', org_name, 'cookbooks', 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

#local_mode_urlObject


152
153
154
155
# File 'lib/chef_zero/server.rb', line 152

def local_mode_url
  raise "Port not yet set, cannot generate URL" unless port.kind_of?(Integer)
  "chefzero://localhost:#{port}"
end

#on_request(&block) ⇒ Object


355
356
357
# File 'lib/chef_zero/server.rb', line 355

def on_request(&block)
  @on_request_proc = block
end

#on_response(&block) ⇒ Object


359
360
361
# File 'lib/chef_zero/server.rb', line 359

def on_response(&block)
  @on_response_proc = block
end

#portInteger

Returns:

  • (Integer)

120
121
122
123
124
125
126
127
128
# File 'lib/chef_zero/server.rb', line 120

def port
  if @port
    @port
  elsif !options[:port].respond_to?(:each)
    options[:port]
  else
    raise "port cannot be determined until server is started"
  end
end

#request_handler(&block) ⇒ Object


467
468
469
# File 'lib/chef_zero/server.rb', line 467

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.

Returns:

  • (Boolean)

    true if the server is accepting requests, false otherwise


316
317
318
# File 'lib/chef_zero/server.rb', line 316

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.

Parameters:

  • publish (Boolean|IO) (defaults to: true)

    publish the server information to the publish parameter or to STDOUT if it's “true”

Returns:

  • (nil)

    this method will block the main thread until interrupted


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/chef_zero/server.rb', line 204

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 Chef Zero (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 Chef Zero..."
      @server.shutdown
    end
  end

  # Move the background process to the main thread
  thread.join
end

#start_background(wait = 5) ⇒ Thread

Start a Chef Zero server in a forked process. This method returns the PID to the forked process.

Parameters:

  • wait (Fixnum) (defaults to: 5)

    the number of seconds to wait for the server to start

Returns:

  • (Thread)

    the thread the background process is running in


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/chef_zero/server.rb', line 246

def start_background(wait = 5)
  @server = WEBrick::HTTPServer.new(
    :DoNotListen => true,
    :AccessLog   => [],
    :Logger      => WEBrick::Log.new(StringIO.new, 7),
    :SSLEnable  => options[:ssl],
    :SSLCertName  => [ [ 'CN', WEBrick::Utils::getservername ] ],
    :StartCallback => proc {
      @running = true
    }
  )
  ENV['HTTPS'] = 'on' if options[:ssl]
  @server.mount('/', Rack::Handler::WEBrick, app)

  # Pick a port
  if options[:port].respond_to?(:each)
    options[:port].each do |port|
      begin
        @server.listen(options[:host], port)
        @port = port
        break
      rescue Errno::EADDRINUSE
        ChefZero::Log.info("Port #{port} in use: #{$!}")
      end
    end
    if !@port
      raise Errno::EADDRINUSE, "No port in :port range #{options[:port]} is available"
    end
  else
    @server.listen(options[:host], options[:port])
    @port = options[:port]
  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.
  while !@running && @thread.alive?
    sleep(0.01)
  end

  SocketlessServerMap.instance.register_port(@port, self)

  @thread
end

#start_socketlessObject


300
301
302
# File 'lib/chef_zero/server.rb', line 300

def start_socketless
  @port = SocketlessServerMap.instance.register_no_listen_server(self)
end

#stop(wait = 5) ⇒ Object

Gracefully stop the Chef Zero server.

Parameters:

  • wait (Fixnum) (defaults to: 5)

    the number of seconds to wait before raising force-terminating the server


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/chef_zero/server.rb', line 327

def stop(wait = 5)
  if @running
    @server.shutdown if @server
    @thread.join(wait) if @thread
  end
rescue Timeout::Error
  if @thread
    ChefZero::Log.error("Chef Zero did not stop within #{wait} seconds! Killing...")
    @thread.kill
    SocketlessServerMap.deregister(port)
  end
ensure
  @server = nil
  @thread = nil
end

#to_sObject


471
472
473
# File 'lib/chef_zero/server.rb', line 471

def to_s
  "#<#{self.class} #{url}>"
end

#urlString

The URL for this Chef Zero server. If the given host is an IPV6 address, it is escaped in brackets according to RFC-2732.

Returns:

  • (String)

See Also:


143
144
145
146
147
148
149
150
# File 'lib/chef_zero/server.rb', line 143

def url
  sch = @options[:ssl] ? 'https' : 'http'
  @url ||= if @options[:host].include?(':')
             URI("#{sch}://[#{@options[:host]}]:#{port}").to_s
           else
             URI("#{sch}://#{@options[:host]}:#{port}").to_s
           end
end