zygote 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/cell_queue.rb +45 -0
- data/lib/chef.rb +24 -0
- data/lib/http.rb +96 -0
- data/lib/memory.rb +26 -0
- data/lib/util.rb +36 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/cell_queue.rb
ADDED
@@ -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
|
data/lib/chef.rb
ADDED
@@ -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']
|
data/lib/http.rb
ADDED
@@ -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
|
data/lib/memory.rb
ADDED
@@ -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
|
data/lib/util.rb
ADDED
@@ -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:
|