zygote 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e95ff9db7b9ed9bf04815b47d7fbc3dd8e96ea1
4
+ data.tar.gz: 154a5dfd45af2c06da4b6e479483ee9520fb8d1e
5
+ SHA512:
6
+ metadata.gz: d537064fa1d974cae1d02f5a535d32b6da73a8963b1282ff295291226f85df3f8df53f6b2585d75832df5a147ad07483150f4831895bc04b43cd66fb2e01efc3
7
+ data.tar.gz: 1b81bad1b0c4e4f96f45a708dbe8e73eef2f59ff42ede695de88d1ad525694fba2d53b6a2c823205e44319fe98933d712ef12ef2b86242ebd7d8c14ad2bcbbbc
@@ -0,0 +1,45 @@
1
+ require 'memory'
2
+
3
+ # An entry into the queue
4
+ class CellQueueEntry < SuperModel::Base
5
+ include SuperModel::Marshal::Model
6
+ end
7
+
8
+ # A means of storing Cell queue data for a given sku
9
+ module CellQueue
10
+ extend self
11
+ COLLECTION = :assets
12
+ ARRAY_KEY = :cell_queue
13
+ Memory.load
14
+
15
+ def push(key, data)
16
+ entry = CellQueueEntry.find_by_name(key)
17
+ unless entry
18
+ entry = CellQueueEntry.new(name: key, data: [])
19
+ entry.save
20
+ end
21
+ entry.data << data
22
+ entry.save
23
+ Memory.save
24
+ end
25
+
26
+ def shift(key)
27
+ entry = CellQueueEntry.find_by_name(key)
28
+ return nil unless entry
29
+ first = entry.data.shift
30
+ entry.save
31
+ Memory.save
32
+ first
33
+ end
34
+
35
+ def show(key)
36
+ entry = CellQueueEntry.find_by_name(key)
37
+ entry ? entry.data : []
38
+ end
39
+
40
+ def purge(key)
41
+ entry = CellQueueEntry.find_by_name(key)
42
+ entry.data = [] if entry
43
+ entry.save if entry
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'chef-provisioner'
2
+
3
+ module ChefConfig
4
+ extend self
5
+ CLIENT_KEY_PATH = File.expand_path('../../tmp/client.pem', __FILE__).freeze
6
+ SECRETS_JSON_PATH = File.expand_path('../../config/secrets.json', __FILE__).freeze
7
+ CHEF_CONFIG_DATA = YAML.load(File.read(File.expand_path('../../config/chef.yml', __FILE__)))['chef'].freeze
8
+
9
+ def load
10
+ make_client_pem
11
+ ChefProvisioner::Chef.configure(endpoint: CHEF_CONFIG_DATA['endpoint'], key_path: CLIENT_KEY_PATH, client: CHEF_CONFIG_DATA['client'])
12
+ end
13
+
14
+ private
15
+
16
+ def make_client_pem
17
+ key_data = JSON.load(File.read(SECRETS_JSON_PATH))['chef_client']
18
+ FileUtils.mkdir_p(File.dirname(CLIENT_KEY_PATH))
19
+ File.write(CLIENT_KEY_PATH, key_data)
20
+ end
21
+
22
+ end
23
+
24
+ ChefConfig.load unless ENV['TESTING']
@@ -0,0 +1,96 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'genesisreactor'
4
+ require 'genesis/protocol/http'
5
+ require 'active_support/all'
6
+
7
+ require 'util'
8
+ require 'cell_queue'
9
+ require 'chef'
10
+
11
+ # Main HTTP class, handles routing methods
12
+ # Uses sinatra format (all sinatra docs on routing methods apply)
13
+ class ZygoteWeb < Genesis::Http::Handler
14
+ CELL_CONFIG = YAML.load(File.read(File.expand_path('../../config/cells.yml', __FILE__))).freeze
15
+
16
+ # Requested by iPXE on boot, chains into /boot.
17
+ # This enables us to customize what details we want iPXE to send us
18
+ # The iPXE undionly.kpxe should contain an embedded script to call this URL
19
+ get '/' do
20
+ body { erb :boot }
21
+ end
22
+
23
+ # Chainload the primary menu
24
+ get '/chain' do
25
+ # Clean params into a simple hash
26
+ cleaned = clean_params(params.to_h)
27
+ # Add the request ip into the params
28
+ ip = request.ip == '127.0.0.1' ? @env['HTTP_X_FORWARDED_FOR'] : request.ip
29
+ ip = '127.0.0.1' if (ENV['TESTING'] || ip.nil? || ip.empty?)
30
+ cleaned['ip'] = ip
31
+ # Compute SKU from parameters
32
+ sku = compute_sku(cleaned['manufacturer'], cleaned['serial'], cleaned['board-serial'])
33
+ cleaned['sku'] = sku
34
+ # Check if there are is any queued data for this SKU, and if so, merge it in to params
35
+ queued_data = CellQueue.shift(sku)
36
+ cleaned.merge!(queued_data) if queued_data
37
+ @channel << cleaned
38
+ body { erb :menu, locals: { opts: CELL_CONFIG.merge('params' => cleaned || {}) } }
39
+ end
40
+
41
+ # Render an action for a particular cell
42
+ get %r{/cell/(?<cell>\S*)/(?<action>\S*)} do
43
+ # Clean params into a simple hash
44
+ cleaned = clean_params(params.to_h)
45
+ # Add the cell to the parameters
46
+ cell = cleaned['cell']
47
+ # Merge the cleaned params in with any cell options
48
+ cell_opts = CELL_CONFIG['index']['cells'][cell] || {}
49
+ opts = cell_opts.merge('params' => cleaned || {})
50
+ @channel << opts # for debugging
51
+ body { erb :"#{cell}/#{cleaned['action']}".to_sym, locals: { opts: opts } }
52
+ end
53
+
54
+ # Show the queue for a SKU
55
+ get %r{/queue/(?<sku>\S*)} do
56
+ body { JSON.pretty_generate(CellQueue.show(params['sku'])) }
57
+ end
58
+
59
+ # Delete the queue for a SKU
60
+ delete '/queue' do
61
+ CellQueue.purge(params['sku'])
62
+ body { JSON.pretty_generate(CellQueue.show(params['sku'])) }
63
+ end
64
+
65
+ # Enable push cells (with optional data) to the cell queue for a SKU
66
+ post %r{/queue/(?<sku>\S*)/(?<cell>\S*)} do
67
+ # Clean params into a simple hash
68
+ cleaned = clean_params(params.to_h)
69
+ # Enqueue some data for this sku
70
+ sku = cleaned.delete('sku')
71
+ CellQueue.push(sku, cleaned)
72
+ body { JSON.pretty_generate(CellQueue.show(sku)) }
73
+ end
74
+
75
+ subscribe do |args|
76
+ puts args if ENV['DEBUG']
77
+ end
78
+ end
79
+
80
+ def zygote
81
+ zygote = Genesis::Reactor.new(
82
+ threads: 1000,
83
+ protocols: {
84
+ Genesis::Http::Protocol => 7000
85
+ },
86
+ handlers: [ZygoteWeb],
87
+ views: [File.join(Dir.pwd, 'views'), File.join(Dir.pwd, 'cells')],
88
+ debug: ENV['DEBUG']
89
+ )
90
+ zygote
91
+ end
92
+
93
+ if ENV['DEBUG']
94
+ $stdout.sync = true
95
+ $stderr.sync = true
96
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ require 'supermodel'
5
+
6
+ # A simple means of persistence
7
+ # This can easily be swapped out for redis, but file-based is simpler and good enough for now
8
+ # https://github.com/maccman/supermodel/blob/master/README
9
+ module Memory
10
+ extend self
11
+ DATABASE_PATH = (ENV['DATABASE_PATH'] || File.expand_path('../../data/memory.db', __FILE__)).freeze
12
+ SuperModel::Marshal.path = DATABASE_PATH
13
+
14
+ def save
15
+ FileUtils.mkdir_p(File.dirname(DATABASE_PATH)) # FIXME - don't make if it already exists
16
+ SuperModel::Marshal.dump
17
+ end
18
+
19
+ def load
20
+ SuperModel::Marshal.load
21
+ end
22
+ end
23
+
24
+ at_exit do
25
+ Memory.save
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'socket'
2
+
3
+ def compute_sku(vendor, serial, board_serial)
4
+ # Sanitize params
5
+ strip_pattern = /[^-^:\p{Alnum}]/
6
+ serial = (serial || '').gsub(strip_pattern, '')
7
+ vendor = (vendor || '').gsub(strip_pattern, '')
8
+ board_serial = (board_serial || '').gsub(strip_pattern, '')
9
+
10
+ serial = board_serial unless board_serial.empty?
11
+
12
+ case vendor
13
+ when 'DellInc'
14
+ sku = 'DEL'
15
+ when 'Supermicro'
16
+ sku = 'SPM'
17
+ else
18
+ sku = 'UKN' # unknown manufacturer
19
+ end
20
+
21
+ sku = "#{sku}-#{serial}"
22
+ sku
23
+ end
24
+
25
+ def clean_params(params)
26
+ params.delete_if { |x, _| x == 'splat' || x == 'captures' }
27
+ params
28
+ end
29
+
30
+ def my_ip
31
+ Socket.ip_address_list.find{|x| x.ipv4? && !x.ipv4_loopback?}.ip_address
32
+ end
33
+
34
+ def discover_domain
35
+ Socket.gethostname.split('.')[1..-1].join('.')
36
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zygote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dale Hamel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Automate baremetal server actions with iPXE
14
+ email: dale.hamel@srvthe.net
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/cell_queue.rb
20
+ - lib/chef.rb
21
+ - lib/http.rb
22
+ - lib/memory.rb
23
+ - lib/util.rb
24
+ homepage: http://rubygems.org/gems/zygote
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.4.6
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Differentiate servers with iPXE
48
+ test_files: []
49
+ has_rdoc: