zygote 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: