stork 0.1.0.pre → 0.2.0.pre

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.ruby-version +1 -1
  4. data/Gemfile.lock +7 -8
  5. data/README.md +521 -4
  6. data/bin/stork +19 -5
  7. data/bin/storkctl +39 -9
  8. data/box/Vagrantfile +41 -0
  9. data/box/bootstrap.sh +317 -0
  10. data/box/integration.sh +33 -0
  11. data/lib/stork/builder.rb +78 -24
  12. data/lib/stork/client/plugins/host_actions.rb +12 -0
  13. data/lib/stork/client/plugins/host_list.rb +1 -1
  14. data/lib/stork/client/plugins/host_reload.rb +16 -0
  15. data/lib/stork/client/plugins/host_show.rb +1 -1
  16. data/lib/stork/client/plugins/host_sync.rb +16 -0
  17. data/lib/stork/collections.rb +1 -0
  18. data/lib/stork/configuration.rb +51 -92
  19. data/lib/stork/database.rb +96 -0
  20. data/lib/stork/deploy/command.rb +8 -0
  21. data/lib/stork/deploy/install_script.rb +3 -5
  22. data/lib/stork/deploy/kickstart_binding.rb +4 -6
  23. data/lib/stork/deploy/section.rb +11 -7
  24. data/lib/stork/deploy/snippet_binding.rb +15 -10
  25. data/lib/stork/plugin.rb +24 -5
  26. data/lib/stork/pxe.rb +2 -2
  27. data/lib/stork/resource/base.rb +0 -2
  28. data/lib/stork/resource/delegator.rb +0 -2
  29. data/lib/stork/resource/host.rb +23 -23
  30. data/lib/stork/resource/logical_volume.rb +1 -1
  31. data/lib/stork/server/application.rb +75 -13
  32. data/lib/stork/server/control.rb +82 -21
  33. data/lib/stork/version.rb +1 -1
  34. data/lib/stork.rb +4 -3
  35. data/specs/builder_spec.rb +5 -1
  36. data/specs/configuration_spec.rb +16 -133
  37. data/specs/database_spec.rb +33 -0
  38. data/specs/deploy_snippet_binding_spec.rb +15 -0
  39. data/specs/kickstart_spec.rb +1 -0
  40. data/specs/pxe_spec.rb +2 -2
  41. data/specs/resource_host_spec.rb +121 -261
  42. data/specs/scripts/kssetup.sh +2 -2
  43. data/specs/server_spec.rb +46 -24
  44. data/specs/spec_helper.rb +3 -2
  45. data/specs/stork/bundles/hosts/example.org.rb +2 -1
  46. data/specs/stork/bundles/public/file.txt +5 -0
  47. data/specs/stork/config.rb +1 -0
  48. data/stork.gemspec +3 -1
  49. metadata +43 -4
@@ -5,6 +5,7 @@ module Stork
5
5
  class Application < Sinatra::Base
6
6
  configure do
7
7
  enable :logging
8
+ disable :show_exceptions
8
9
  mime_type :plain, 'text/plain'
9
10
  mime_type :json, 'application/json'
10
11
  end
@@ -15,10 +16,23 @@ module Stork
15
16
  end
16
17
 
17
18
  get '/' do
18
- info 'GET /'
19
+ loginfo 'GET /'
19
20
  json_halt 200, 200, "Stork Version #{VERSION} - #{CODENAME}"
20
21
  end
21
22
 
23
+ get '/api/v1/reload' do
24
+ reload_collection
25
+ end
26
+
27
+ get '/api/v1/actions' do
28
+ h = {'hosts' => database.hosts}
29
+ json_halt_ok_with_content(h.to_json)
30
+ end
31
+
32
+ get '/api/v1/sync' do
33
+ sync_collection
34
+ end
35
+
22
36
  get '/api/v1/hosts' do
23
37
  h = {'hosts' => hosts.hashify}
24
38
  json_halt_ok_with_content(h.to_json)
@@ -26,26 +40,32 @@ module Stork
26
40
 
27
41
  get '/api/v1/host/:host' do |host|
28
42
  h = hosts.get(host)
29
- json_halt_ok_with_content(h.hashify.to_json)
43
+ if h
44
+ json_halt_ok_with_content(h.hashify.to_json)
45
+ else
46
+ json_halt_not_found
47
+ end
30
48
  end
31
49
 
32
50
  get '/host/:host' do |host|
33
- info "#{host} requested kickstart"
51
+ loginfo "#{host} requested kickstart"
34
52
 
35
53
  h = hosts.get(host)
36
54
 
37
- if h
55
+ if h && is_installable?(host)
56
+ loginfo "Returned kickstart to #{host}"
38
57
  # hmm, should the host deploy?
39
58
  ks = Stork::Deploy::InstallScript.new(h) # we will be passing the type in shortly
40
59
  content_type :plain
41
60
  ks.render
42
61
  else
62
+ logerr "Was not able to return kickstart"
43
63
  json_halt_not_found
44
64
  end
45
65
  end
46
66
 
47
67
  get '/host/:host/installed' do |host|
48
- info "#{host} has notified completed install"
68
+ loginfo "#{host} has notified completed install"
49
69
  h = hosts.get(host)
50
70
 
51
71
  if h
@@ -57,7 +77,7 @@ module Stork
57
77
  end
58
78
 
59
79
  get '/host/:host/install' do |host|
60
- info "install requested for #{host}"
80
+ loginfo "install requested for #{host}"
61
81
  h = hosts.get(host)
62
82
 
63
83
  if h
@@ -68,35 +88,77 @@ module Stork
68
88
  end
69
89
  end
70
90
 
91
+ get '/public/:filename' do |file|
92
+ send_file public_path(file)
93
+ end
94
+
71
95
  not_found do
72
96
  json_halt_not_found
73
97
  end
74
98
 
99
+ error do
100
+ json_halt_internal_error
101
+ end
102
+
75
103
  helpers do
76
104
  def hosts
77
- @hosts ||= settings.collection.hosts
105
+ settings.collection.hosts
78
106
  end
79
107
 
80
108
  def config
81
- @config ||= settings.config
109
+ settings.config
110
+ end
111
+
112
+ def collection
113
+ settings.collection
114
+ end
115
+
116
+ def database
117
+ settings.database
118
+ end
119
+
120
+ def is_installable?(host)
121
+ database.host(host)[:action] == 'install'
122
+ end
123
+
124
+ def reload_collection
125
+ settings.collection = Stork::Builder.load.collection
126
+ json_halt 200, 200, 'OK'
127
+ end
128
+
129
+ def sync_collection
130
+ # make sure we reload the collection to pick up changes
131
+ settings.collection = Stork::Builder.load.collection
132
+ settings.database.sync_hosts(hosts)
133
+ json_halt 200, 200, 'OK'
82
134
  end
83
135
 
84
136
  def set_localboot(host)
137
+ database.boot_local(host.name)
85
138
  pxe(host).localboot
86
139
  end
87
140
 
88
141
  def set_install(host)
142
+ database.boot_install(host.name)
89
143
  pxe(host).install
90
144
  end
91
145
 
92
146
  def pxe(host)
93
- Stork::PXE.new(host, host.stork, config.port)
147
+ Stork::PXE.new(host, host.stork, Configuration.port)
94
148
  end
95
149
 
96
- def info(msg)
150
+ def public_path(filename)
151
+ File.join(Configuration.public_path, filename)
152
+ end
153
+
154
+ def loginfo(msg)
97
155
  logger.info "[#{request.ip}] INFO: #{msg}"
98
156
  end
99
157
 
158
+ def logerr(msg)
159
+ logger.error "[#{request.ip}] ERROR: #{msg}"
160
+ end
161
+
100
162
  def json_halt(request_status, op_status, message)
101
163
  content_type :json
102
164
  halt request_status, { 'Content-Type' => 'application/json' }, "{ \"status\":\"#{op_status}\", \"message\": \"#{message}\" }"
@@ -112,14 +174,14 @@ module Stork
112
174
  halt 200, { 'Content-Type' => 'application/json' }, content
113
175
  end
114
176
 
115
- def json_halt_internal_error
177
+ def json_halt_internal_error(content="Internal error")
116
178
  content_type :json
117
- json_halt 500, 500, 'Internal error'
179
+ json_halt 500, 500, content
118
180
  end
119
181
 
120
182
  def json_halt_not_found
121
183
  content_type :json
122
- json_halt 404, 404, 'not found'
184
+ json_halt 404, 404, 'Not found'
123
185
  end
124
186
  end
125
187
  end
@@ -1,37 +1,98 @@
1
1
  require 'sinatra'
2
2
  require 'json'
3
3
  require 'rack'
4
- require 'thin'
4
+ require 'webrick'
5
5
 
6
6
  module Stork
7
7
  module Server
8
8
  class Control
9
- def self.start(configuration, collection)
10
- puts "Starting stork server on port #{configuration.port}."
11
- app = Stork::Server::Application
12
- app.set :collection, collection
13
- app.set :config, configuration
14
-
15
- @thin = Thin::Server.new(configuration.bind, configuration.port, app)
16
- @thin.tag = "Stork #{VERSION}"
17
- unless ENV['RACK_ENV'] == 'test'
18
- @thin.pid_file = configuration.pid_file
19
- @thin.log_file = configuration.log_file
20
- @thin.daemonize
9
+ def initialize(options)
10
+ @app = Stork::Server::Application
11
+ @daemonize = options.daemonize
12
+ end
13
+
14
+ def start
15
+ @collection = Stork::Builder.load.collection
16
+
17
+ @db = Stork::Database.load(Configuration[:db_path])
18
+ @db.sync_hosts(@collection.hosts)
19
+
20
+ @app.set :collection, @collection
21
+ @app.set :database, @db
22
+
23
+ unless is_daemon?
24
+ puts <<-EOH.gsub(/^ {12}/, '')
25
+ >> Starting Stork Server (#{Stork::VERSION})...
26
+ >> WEBrick (#{WEBrick::VERSION}) with Rack (#{Rack.release}) is listening on #{Configuration[:bind]}:#{Configuration[:port]}
27
+ >> Press CTRL+C to stop
28
+
29
+ EOH
21
30
  else
22
- puts '!!! Running in test mode'
31
+ emit_process_id
32
+ end
33
+
34
+ thread = start_background
35
+ %w[INT TERM].each { |signal| trap_signal(signal) }
36
+ thread.join
37
+ end
38
+
39
+ def emit_process_id
40
+ File.open(Configuration[:pid_file], 'w') { |f| f.write(Process.pid) }
41
+ end
42
+
43
+ def trap_signal(signal)
44
+ Signal.trap(signal) do
45
+ puts "\n>> Stopping Stork..."
46
+ @server.shutdown
23
47
  end
24
- @thin.start
25
48
  end
26
49
 
27
- def self.stop(configuration, collection)
28
- puts 'Stoping the stork server.'
29
- Thin::Server.kill(configuration.pid_file)
50
+ ####
51
+ # Basic start management borrowed from chef-zero. Thanks for the
52
+ # heavy lifting! Could there be a way to utilize any light http
53
+ # servers (thin, puma, unicorn)?
54
+ ####
55
+ def start_background
56
+ @server = WEBrick::HTTPServer.new(
57
+ :BindAddress => Configuration[:bind],
58
+ :Port => Configuration[:port],
59
+ :AccessLog => [],
60
+ :Logger => WEBrick::Log.new(StringIO.new, 7),
61
+ :StartCallback => proc { @running = true }
62
+ )
63
+ @server.mount('/', Rack::Handler::WEBrick, @app)
64
+
65
+ @thread = Thread.new do
66
+ begin
67
+ Thread.current.abort_on_exception = true
68
+ @server.start
69
+ ensure
70
+ @running = false
71
+ end
72
+ end
73
+
74
+ while !@running && @thread.alive?
75
+ sleep(0.01)
76
+ end
77
+ @thread
78
+ end
79
+
80
+ def is_daemon?
81
+ @daemonize
82
+ end
83
+
84
+ def stop
85
+ pid = File.read(Configuration[:pid_file]).to_i
86
+ Process.kill('TERM', pid)
87
+ rescue Errno::ENOENT
88
+ puts "Process ID file not present."
89
+ rescue Errno::ESRCH
90
+ puts "Stork is not running."
30
91
  end
31
92
 
32
- def self.restart(configuration, collection)
33
- stop(configuration, collection)
34
- start(configuration, collection)
93
+ def restart
94
+ stop
95
+ start
35
96
  end
36
97
  end
37
98
  end
data/lib/stork/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Stork
2
- VERSION = '0.1.0.pre'
2
+ VERSION = '0.2.0.pre'
3
3
  CODENAME = 'Psychotic Hamster'
4
4
  end
data/lib/stork.rb CHANGED
@@ -16,15 +16,16 @@ require 'stork/deploy/kickstart_binding'
16
16
  require 'stork/deploy/install_script'
17
17
 
18
18
  require 'stork/pxe'
19
+ require 'stork/database'
19
20
 
20
21
  require 'stork/server/application'
21
22
  require 'stork/server/control'
22
23
 
23
24
  require 'stork/plugin'
24
- Dir[File.join('./lib/stork/client/plugins', '*.rb')].each do |plugin|
25
- require plugin
26
25
 
27
- Stork.add_plugin(plugin)
26
+ path = File.expand_path(File.dirname(__FILE__))
27
+ Dir[File.join(path, '/stork/client/plugins', '*.rb')].each do |plugin|
28
+ require plugin
28
29
  end
29
30
 
30
31
  # Also make sure we allow a .stork/plugins dir
@@ -1,8 +1,12 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe "Stork::Builder" do
4
+ before(:each) do
5
+ load_config
6
+ end
7
+
4
8
  it "must load everything" do
5
- builder = Stork::Builder.load(configuration)
9
+ builder = Stork::Builder.load
6
10
  collection = builder.collection
7
11
  collection.hosts.size.must_equal 11
8
12
  collection.chefs.size.must_equal 1
@@ -10,141 +10,24 @@ describe "Stork::Configuration" do
10
10
  FileUtils.rm_rf(Dir.glob("#{@path}/tmp/*"))
11
11
  end
12
12
 
13
- it "should respond to path accessors" do
14
- Stork::Configuration.new.must_respond_to :path
15
- Stork::Configuration.new.must_respond_to :path=
16
- end
17
-
18
- it "should respond to bundles accessors" do
19
- Stork::Configuration.new.must_respond_to :bundle_path
20
- Stork::Configuration.new.must_respond_to :bundle_path=
21
- end
22
-
23
- it "should respond to hosts_path" do
24
- Stork::Configuration.new.must_respond_to :hosts_path
25
- end
26
-
27
- it "should respond to snippets_path" do
28
- Stork::Configuration.new.must_respond_to :snippets_path
29
- end
30
-
31
- it "should respond to layouts_path accessors" do
32
- Stork::Configuration.new.must_respond_to :layouts_path
33
- end
34
-
35
- it "should respond to networks_path accessors" do
36
- Stork::Configuration.new.must_respond_to :networks_path
37
- end
38
-
39
- it "should respond to distros_path accessors" do
40
- Stork::Configuration.new.must_respond_to :distros_path
41
- end
42
-
43
- it "should respond to templates_path accessors" do
44
- Stork::Configuration.new.must_respond_to :templates_path
45
- end
46
-
47
- it "should respond to chefs_path accessors" do
48
- Stork::Configuration.new.must_respond_to :chefs_path
49
- end
50
-
51
- it "should respond to authorized_keys_file accessors" do
52
- Stork::Configuration.new.must_respond_to :authorized_keys_file
53
- Stork::Configuration.new.must_respond_to :authorized_keys_file=
54
- end
55
-
56
- it "should respond to pxe_path accessors" do
57
- Stork::Configuration.new.must_respond_to :pxe_path
58
- Stork::Configuration.new.must_respond_to :pxe_path=
59
- end
60
-
61
- it "should respond to log_file accessors" do
62
- Stork::Configuration.new.must_respond_to :log_file
63
- Stork::Configuration.new.must_respond_to :log_file=
64
- end
65
-
66
- it "should respond to pid_file accessors" do
67
- Stork::Configuration.new.must_respond_to :pid_file
68
- Stork::Configuration.new.must_respond_to :pid_file=
69
- end
70
-
71
- it "should respond to server accessors" do
72
- Stork::Configuration.new.must_respond_to :server
73
- Stork::Configuration.new.must_respond_to :server=
74
- end
75
-
76
- it "should respond to port accessors" do
77
- Stork::Configuration.new.must_respond_to :port
78
- Stork::Configuration.new.must_respond_to :port=
79
- end
80
-
81
- it "should respond to bind accessors" do
82
- Stork::Configuration.new.must_respond_to :bind
83
- Stork::Configuration.new.must_respond_to :bind=
84
- end
85
-
86
- it "should respond to timezone accessors" do
87
- Stork::Configuration.new.must_respond_to :timezone
88
- Stork::Configuration.new.must_respond_to :timezone=
89
- end
90
-
91
- it "should have the defaults" do
92
- config = Stork::Configuration.new
93
- config.path.must_equal "/etc/stork"
94
- config.bundle_path.must_equal "/etc/stork/bundles"
95
- config.hosts_path.must_equal "/etc/stork/bundles/hosts"
96
- config.layouts_path.must_equal "/etc/stork/bundles/layouts"
97
- config.snippets_path.must_equal "/etc/stork/bundles/snippets"
98
- config.networks_path.must_equal "/etc/stork/bundles/networks"
99
- config.distros_path.must_equal "/etc/stork/bundles/distros"
100
- config.chefs_path.must_equal "/etc/stork/bundles/chefs"
101
- config.templates_path.must_equal "/etc/stork/bundles/templates"
102
- config.authorized_keys_file.must_equal "/etc/stork/authorized_keys"
103
- config.pxe_path.must_equal "/var/lib/tftpboot/pxelinux.cfg"
104
- config.log_file.must_equal "/var/log/stork.log"
105
- config.pid_file.must_equal "/var/run/stork.pid"
106
- config.server.must_equal "localhost"
107
- config.port.must_equal 9293
108
- config.bind.must_equal "0.0.0.0"
109
- config.timezone.must_equal "America/Los_Angeles"
110
- end
111
-
112
- it "should load from file" do
113
- config = Stork::Configuration.from_file("./specs/stork/config.rb")
114
- config.path.must_equal "./specs/stork"
115
- config.bundle_path.must_equal "./specs/stork/bundles"
116
- config.hosts_path.must_equal "./specs/stork/bundles/hosts"
117
- config.layouts_path.must_equal "./specs/stork/bundles/layouts"
118
- config.snippets_path.must_equal "./specs/stork/bundles/snippets"
119
- config.networks_path.must_equal "./specs/stork/bundles/networks"
120
- config.distros_path.must_equal "./specs/stork/bundles/distros"
121
- config.chefs_path.must_equal "./specs/stork/bundles/chefs"
122
- config.templates_path.must_equal "./specs/stork/bundles/templates"
123
- config.authorized_keys_file.must_equal "./specs/stork/authorized_keys"
124
- config.pxe_path.must_equal "./specs/tmp/pxeboot"
125
- config.log_file.must_equal "./specs/tmp/stork.log"
126
- config.pid_file.must_equal "./specs/tmp/stork.pid"
127
- config.server.must_equal "localhost"
128
- config.port.must_equal 5000
129
- config.bind.must_equal "0.0.0.0"
130
- config.timezone.must_equal "America/New_York"
131
- end
132
-
133
13
  it "should create a file if the configuration file does not exist" do
134
- config = Stork::Configuration.from_file("./specs/tmp/config.rb")
14
+ Stork::Configuration.reset
15
+ Stork::Configuration.to_file("./specs/tmp/config.rb")
16
+ Stork::Configuration.from_file("./specs/tmp/config.rb")
135
17
  File.exist?("./specs/tmp/config.rb").must_equal true
136
- expected_content = <<-EOS
137
- # Stork configuration file"
138
- path "/etc/stork"
139
- bundle_path "/etc/stork/bundles"
140
- authorized_keys_file "/etc/stork/authorized_keys"
141
- pxe_path "/var/lib/tftpboot/pxelinux.cfg"
142
- log_file "/var/log/stork.log"
143
- pid_file "/var/run/stork.pid"
144
- server "localhost"
145
- port 9293
146
- bind "0.0.0.0"
147
- timezone "America/Los_Angeles"
18
+ expected_content = <<-EOS.gsub(/^ {6}/, '')
19
+ # Stork configuration file
20
+ path "/etc/stork"
21
+ bundle_path "/etc/stork/bundles"
22
+ authorized_keys_file "/etc/stork/authorized_keys"
23
+ pxe_path "/var/lib/tftpboot/pxelinux.cfg"
24
+ db_path "/var/lib/stork"
25
+ log_file "/var/log/stork.log"
26
+ pid_file "/var/run/stork.pid"
27
+ server "localhost"
28
+ port 9293
29
+ bind "0.0.0.0"
30
+ timezone "America/Los_Angeles"
148
31
  EOS
149
32
  File.read("./specs/tmp/config.rb").must_equal expected_content
150
33
  end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe "Stork::PXE" do
5
+ before(:each) do
6
+ @db = Stork::Database.load('./specs/tmp')
7
+ @db.sync_hosts(collection.hosts)
8
+ end
9
+
10
+ after(:each) do
11
+ FileUtils.rm_rf(Dir.glob('./specs/tmp/*'))
12
+ end
13
+
14
+ it "contains and can access all of the hosts" do
15
+ collection.hosts.each do |host|
16
+ result = @db.host(host.name)
17
+ result[:name].must_equal host.name
18
+ result[:action].must_equal 'localboot'
19
+ end
20
+ end
21
+
22
+ it "can change the boot action and then change it back" do
23
+ @db.boot_install('c013.example.org')
24
+ result = @db.host('c013.example.org')
25
+ result[:name].must_equal 'c013.example.org'
26
+ result[:action].must_equal 'install'
27
+
28
+ @db.boot_local('c013.example.org')
29
+ result = @db.host('c013.example.org')
30
+ result[:name].must_equal 'c013.example.org'
31
+ result[:action].must_equal 'localboot'
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe "Stork::Deploy::SnippetBinding" do
5
+ before(:each) do
6
+ load_config
7
+ end
8
+
9
+ it "must create a valid first boot file for chef" do
10
+ host = collection.hosts.get("server.example.org")
11
+ binding = Stork::Deploy::SnippetBinding.new(host)
12
+ first_boot_content = "{\"run_list\":[\"role[base]\",\"recipe[apache]\"]}"
13
+ binding.first_boot_content.must_equal first_boot_content
14
+ end
15
+ end
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe "Stork::Deploy::Kickstart" do
4
4
  before(:each) do
5
+ load_config
5
6
  @host = collection.hosts.get("server.example.org")
6
7
  @path = File.dirname(__FILE__)
7
8
  FileUtils.mkdir_p "#{@path}/tmp"
data/specs/pxe_spec.rb CHANGED
@@ -22,7 +22,7 @@ TIMEOUT 0
22
22
  TOTALTIMEOUT 0
23
23
  ONTIMEOUT local
24
24
  LABEL local
25
- LOCALBOOT -1
25
+ LOCALBOOT 0
26
26
  EOS
27
27
  File.read("#{@path}/01-00-11-22-33-44-55").must_equal expected_content
28
28
  end
@@ -36,7 +36,7 @@ TIMEOUT 0
36
36
  TOTALTIMEOUT 0
37
37
  ONTIMEOUT local
38
38
  LABEL local
39
- LOCALBOOT -1
39
+ LOCALBOOT 0
40
40
  EOS
41
41
  File.read("#{@path}/01-00-11-22-33-44-55").must_equal expected_content
42
42
  end