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,7 +5,7 @@ module HostShowPlugin
5
5
  def run
6
6
  host = args.shift
7
7
  raise SyntaxError, "A host must be supplied" if host.nil?
8
- data = fetch("/host/#{host}")
8
+ data = fetch("host/#{host}")
9
9
  show('name', data)
10
10
  show('distro', data)
11
11
  show('template', data)
@@ -0,0 +1,16 @@
1
+ module HostSyncPlugin
2
+ class HostSync < Stork::Plugin
3
+ banner "stork host sync (options)"
4
+
5
+ def run
6
+ response = RestClient.get "#{stork}/api/v1/sync"
7
+ if response.code == 200
8
+ puts "OK"
9
+ else
10
+ data = JSON.parse(response)
11
+ puts "Error: #{data['message']}"
12
+ exit 1
13
+ end
14
+ end
15
+ end
16
+ end
@@ -8,6 +8,7 @@ require 'stork/collection/snippets'
8
8
  require 'stork/collection/templates'
9
9
 
10
10
  module Stork
11
+ # A simple container for all of the resource collections
11
12
  class Collections
12
13
  attr_reader :hosts
13
14
  attr_reader :layouts
@@ -1,98 +1,57 @@
1
+ require 'mixlib/config'
2
+
1
3
  module Stork
2
4
  class Configuration
3
- attr_accessor :path
4
- attr_accessor :bundle_path
5
- attr_accessor :authorized_keys_file
6
- attr_accessor :pxe_path
7
- attr_accessor :log_file
8
- attr_accessor :pid_file
9
- attr_accessor :server
10
- attr_accessor :port
11
- attr_accessor :bind
12
- attr_accessor :timezone
13
-
14
- def initialize
15
- @path = '/etc/stork'
16
- @authorized_keys_file = path + '/authorized_keys'
17
- @bundle_path = path + '/bundles'
18
-
19
- @pxe_path = '/var/lib/tftpboot/pxelinux.cfg'
20
- @log_file = '/var/log/stork.log'
21
- @pid_file = '/var/run/stork.pid'
22
-
23
- @server = 'localhost'
24
- @port = 9293
25
- @bind = '0.0.0.0'
26
- @timezone = 'America/Los_Angeles'
27
- end
28
-
29
- def hosts_path
30
- bundle_path + '/hosts'
31
- end
32
-
33
- def snippets_path
34
- bundle_path + '/snippets'
35
- end
36
-
37
- def layouts_path
38
- bundle_path + '/layouts'
39
- end
40
-
41
- def networks_path
42
- bundle_path + '/networks'
43
- end
44
-
45
- def distros_path
46
- bundle_path + '/distros'
47
- end
48
-
49
- def templates_path
50
- bundle_path + '/templates'
51
- end
52
-
53
- def chefs_path
54
- bundle_path + '/chefs'
55
- end
56
-
57
- def to_file
58
- <<-EOS
59
- # Stork configuration file"
60
- path "#{path}"
61
- bundle_path "#{bundle_path}"
62
- authorized_keys_file "#{authorized_keys_file}"
63
- pxe_path "#{pxe_path}"
64
- log_file "#{log_file}"
65
- pid_file "#{pid_file}"
66
- server "#{server}"
67
- port #{port}
68
- bind "#{bind}"
69
- timezone "#{timezone}"
5
+ extend Mixlib::Config
6
+ config_strict_mode true
7
+
8
+ default :path, '/etc/stork'
9
+ default :bundle_path, '/etc/stork/bundles'
10
+ default :authorized_keys_file, '/etc/stork/authorized_keys'
11
+ default :pxe_path, '/var/lib/tftpboot/pxelinux.cfg'
12
+ default :log_file, '/var/log/stork.log'
13
+ default :pid_file, '/var/run/stork.pid'
14
+ default :timezone, 'America/Los_Angeles'
15
+ default :db_path, '/var/lib/stork'
16
+
17
+ default :server, 'localhost'
18
+ default :port, 9293
19
+ default :bind, '0.0.0.0'
20
+
21
+ def self.relative_to_bundle_path(path)
22
+ File.join(self.configuration[:bundle_path], path)
23
+ end
24
+
25
+ default(:hosts_path) { relative_to_bundle_path('hosts') }
26
+ default(:snippets_path) { relative_to_bundle_path('snippets') }
27
+ default(:layouts_path) { relative_to_bundle_path('layouts') }
28
+ default(:networks_path) { relative_to_bundle_path('networks') }
29
+ default(:templates_path) { relative_to_bundle_path('templates') }
30
+ default(:chefs_path) { relative_to_bundle_path('chefs') }
31
+ default(:distros_path) { relative_to_bundle_path('distros') }
32
+ default(:public_path) { relative_to_bundle_path('public') }
33
+ default(:client_name) { 'root' }
34
+ default(:client_key) { '~/.stork/root.pem' }
35
+ default(:stork_server_url) { "http://#{self.configuration[:server]}:#{self.configuration[:port]}" }
36
+
37
+ # Only used for the server. Clients will use a plugin to
38
+ # interactively set the parameters or create it manually.
39
+ def self.to_file(config_path)
40
+ content = <<-EOS.gsub(/^ {8}/, '')
41
+ # Stork configuration file
42
+ path "#{path}"
43
+ bundle_path "#{bundle_path}"
44
+ authorized_keys_file "#{authorized_keys_file}"
45
+ pxe_path "#{pxe_path}"
46
+ db_path "#{db_path}"
47
+ log_file "#{log_file}"
48
+ pid_file "#{pid_file}"
49
+ server "#{server}"
50
+ port #{port}
51
+ bind "#{bind}"
52
+ timezone "#{timezone}"
70
53
  EOS
71
- end
72
-
73
- def self.from_file(filename)
74
- find_or_create(filename)
75
- end
76
-
77
- def self.find_or_create(filename)
78
- config = new
79
- if File.exist?(filename)
80
- delegator = ConfigDelegator.new(config)
81
- delegator.instance_eval(File.read(filename), filename)
82
- else
83
- File.open(filename, 'w') { |file| file.write(config.to_file) }
84
- end
85
- config
86
- end
87
-
88
- class ConfigDelegator
89
- def initialize(obj)
90
- @delegated = obj
91
- end
92
-
93
- def method_missing(meth, *args)
94
- @delegated.send("#{meth}=", *args)
95
- end
54
+ File.open(config_path,'w') { |f| f.write(content) }
96
55
  end
97
56
  end
98
57
  end
@@ -0,0 +1,96 @@
1
+ require 'sqlite3'
2
+
3
+ module Stork
4
+ class Database
5
+ def initialize(dbpath)
6
+ unless Dir.exists?(dbpath)
7
+ FileUtils.mkdir_p(dbpath)
8
+ end
9
+
10
+ @db = SQLite3::Database.open(File.join(dbpath, 'stork.db'))
11
+ end
12
+
13
+ def create_tables
14
+ sql = <<-SQL
15
+ CREATE TABLE IF NOT EXISTS hosts(
16
+ name TEXT PRIMARY KEY,
17
+ action TEXT
18
+ )
19
+ SQL
20
+ execute sql
21
+ end
22
+
23
+ def execute(sql, *args)
24
+ @db.execute(sql, args)
25
+ end
26
+
27
+ def find(sql, *args)
28
+ h = Array.new
29
+
30
+ stmt = @db.prepare(sql)
31
+ stmt.execute(*args) do |rows|
32
+ rows.each do |row|
33
+ h << {
34
+ :name => row[0],
35
+ :action => row[1]
36
+ }
37
+ end
38
+ end
39
+
40
+ h
41
+ end
42
+
43
+ def find_one(sql, *args)
44
+ stmt = @db.prepare(sql)
45
+ result = stmt.execute(*args)
46
+
47
+ if result
48
+ result.first
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ def host(name)
55
+ sql = 'SELECT * FROM hosts WHERE name=?'
56
+ host = find_one(sql, name)
57
+
58
+ if host
59
+ { :name => host[0], :action => host[1] }
60
+ else
61
+ nil
62
+ end
63
+ end
64
+
65
+ def hosts
66
+ sql = 'SELECT * FROM hosts ORDER BY name ASC'
67
+ find(sql)
68
+ end
69
+
70
+ def boot_install(name)
71
+ sql = "UPDATE hosts SET action='install' WHERE name=?"
72
+ execute sql, name
73
+ end
74
+
75
+ def boot_local(name)
76
+ sql = "UPDATE hosts SET action='localboot' WHERE name=?"
77
+ execute sql, name
78
+ end
79
+
80
+ def sync_hosts(hosts)
81
+ hosts.each do |h|
82
+ result = host(h.name)
83
+ unless result
84
+ sql = "INSERT INTO hosts(name,action) VALUES(?, 'localboot')"
85
+ execute sql, h.name
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.load(dbpath)
91
+ @db = new(dbpath).tap do |d|
92
+ d.create_tables
93
+ end
94
+ end
95
+ end
96
+ end
@@ -29,6 +29,14 @@ module Stork
29
29
  end
30
30
  end
31
31
 
32
+ def option_no_equal(opt, value)
33
+ if value.is_a?(Array)
34
+ @options << "--#{opt} #{value.join(',')}" unless value.empty?
35
+ else
36
+ @options << "--#{opt} #{value}" if value
37
+ end
38
+ end
39
+
32
40
  def yes_no(opt, value)
33
41
  @options << "--#{opt}=#{value ? 'yes' : 'no'}" if value
34
42
  end
@@ -1,12 +1,11 @@
1
1
  module Stork
2
2
  module Deploy
3
3
  class InstallScript
4
- attr_reader :type, :host, :configuration
4
+ attr_reader :type, :host
5
5
 
6
6
  def initialize(host, type=:kickstart)
7
7
  @host = host
8
8
  @type = type
9
- @configuration = host.configuration
10
9
  end
11
10
 
12
11
  def render
@@ -21,13 +20,12 @@ module Stork
21
20
  def_delegators :@builder, :url, :network, :password, :firewall,
22
21
  :timezone, :selinux, :layout, :partitions, :repos, :volume_groups,
23
22
  :packages, :pre_snippets, :post_snippets
24
- attr_reader :host, :configuration
23
+ attr_reader :host
25
24
 
26
25
  def initialize(type, host)
27
26
  # puts Stork::Deploy::Commands.constants.inspect
28
27
  @builder = Stork::Deploy.const_get("#{type.to_s.capitalize}Binding").new(host)
29
- @configuration = configuration
30
- @host = host.configuration
28
+ @host = host
31
29
  end
32
30
 
33
31
  def get_binding
@@ -1,16 +1,15 @@
1
1
  module Stork
2
2
  module Deploy
3
3
  class KickstartBinding
4
- attr_reader :host, :configuration
4
+ attr_reader :host
5
5
 
6
6
  def initialize(host)
7
7
  @host = host
8
- @configuration = host.configuration
9
8
  end
10
9
 
11
10
  def url
12
11
  Command.create 'url' do |c|
13
- c.option 'url', host.distro.url
12
+ c.option_no_equal 'url', host.distro.url
14
13
  end
15
14
  end
16
15
 
@@ -211,9 +210,8 @@ module Stork
211
210
  # Render me!!!
212
211
  renderer = ERB.new(snippet.content, nil, '-')
213
212
  lines << renderer.result(
214
- Stork::Deploy::SnippetBinding.new(
215
- @configuration, host).get_binding
216
- )
213
+ Stork::Deploy::SnippetBinding.new(host).get_binding
214
+ )
217
215
  end
218
216
  lines.join("\n")
219
217
  end
@@ -4,20 +4,24 @@ module Stork
4
4
  def initialize(name, options)
5
5
  @name = name
6
6
  @options = options
7
- @contents = Array.new
7
+ @contents = []
8
8
  end
9
9
 
10
10
  def to_s
11
- str = "%#{@name}"
12
- @options.each { |key, value| str += " --#{key}=#{value}" }
13
- str += "\n"
14
- str += @contents.join("\n")
15
- str += "\n%end"
11
+ if @contents.empty?
12
+ str = ""
13
+ else
14
+ str = "%#{@name}"
15
+ @options.each { |key, value| str += " --#{key}=#{value}" }
16
+ str += "\n"
17
+ str += @contents.join("\n")
18
+ str += "\n%end"
19
+ end
16
20
  str
17
21
  end
18
22
 
19
23
  def content(content)
20
- @contents << content
24
+ @contents << content unless content.empty?
21
25
  end
22
26
 
23
27
  def self.create(name, opts={}, &block)
@@ -3,9 +3,8 @@ module Stork
3
3
  class SnippetBinding
4
4
  attr_reader :host
5
5
 
6
- def initialize(configuration, host)
6
+ def initialize(host)
7
7
  @host = host
8
- @configuration = configuration
9
8
  end
10
9
 
11
10
  def get_binding
@@ -17,11 +16,12 @@ module Stork
17
16
  end
18
17
 
19
18
  def authorized_keys
20
- File.read(@configuration.authorized_keys_file)
19
+ File.read(Configuration[:authorized_keys_file])
21
20
  end
22
21
 
23
22
  def first_boot_content
24
- run_list = { 'run_list' => host.run_list }
23
+ run_list = {}
24
+ run_list['run_list'] = host.run_list
25
25
  run_list.to_json
26
26
  end
27
27
 
@@ -33,17 +33,22 @@ module Stork
33
33
  host.interfaces.map { |x| x.search_paths }.uniq.flatten
34
34
  end
35
35
 
36
- def midwife_server
37
- @configuration.server
36
+ def stork_server
37
+ Configuration[:server]
38
38
  end
39
39
 
40
- def midwife_port
41
- @configuration.port
40
+ def stork_port
41
+ Configuration[:port]
42
42
  end
43
43
 
44
- def midwife_bind
45
- @configuration.bind
44
+ def stork_bind
45
+ Configuration[:bind]
46
46
  end
47
+
48
+ # Add aliases for old midwife methods
49
+ alias_method :midwife_server, :stork_server
50
+ alias_method :midwife_port, :stork_port
51
+ alias_method :midwife_bind, :stork_bind
47
52
  end
48
53
  end
49
54
  end
data/lib/stork/plugin.rb CHANGED
@@ -23,12 +23,19 @@ module Stork
23
23
  @banner
24
24
  end
25
25
 
26
- def initialize(configuration, options, args)
26
+ def initialize(options, args)
27
27
  @action = nil
28
- @configuration = configuration
29
28
  @options = options
30
29
  @args = args
31
- @stork = "http://#{configuration.server}:#{configuration.port}"
30
+ @stork = "http://#{Configuration[:server]}:#{Configuration[:port]}"
31
+ end
32
+
33
+ def config
34
+ Stork::Configuration.client
35
+ end
36
+
37
+ def url
38
+ Stork::Configuration.url
32
39
  end
33
40
 
34
41
  def fetch(path)
@@ -36,11 +43,23 @@ module Stork
36
43
  data = JSON.parse(response)
37
44
  end
38
45
 
46
+ def print(first, second, options = {})
47
+ padding = options.has_key?(:pad) ? options[:pad] : 16
48
+ say("<%= color('#{first.ljust(padding)}:', CYAN) %> #{second}")
49
+ end
50
+
39
51
  def show(key, data, options = {})
40
- padding = options.has_key?('pad') ? options['pad'] : 16
52
+ padding = options.has_key?(:pad) ? options[:pad] : 16
41
53
  name = key.split('_').map{|k| k.capitalize}.join(' ').ljust(padding)
54
+
42
55
  if data[key].is_a?(Array)
43
- value = data[key].join(', ')
56
+ if data[key].empty?
57
+ value = "** empty **"
58
+ else
59
+ value = data[key].join(', ')
60
+ end
61
+ elsif data[key].is_a?(NilClass)
62
+ value = "Unset"
44
63
  else
45
64
  value = data[key]
46
65
  end
data/lib/stork/pxe.rb CHANGED
@@ -15,7 +15,7 @@ module Stork
15
15
  @hostname = host.name
16
16
  @initrd = host.distro.image
17
17
  @kernel = host.distro.kernel
18
- @path = host.configuration.pxe_path
18
+ @path = Configuration[:pxe_path]
19
19
  @mac = host.pxemac
20
20
  @server = server
21
21
  @port = port
@@ -53,7 +53,7 @@ TIMEOUT 0
53
53
  TOTALTIMEOUT 0
54
54
  ONTIMEOUT local
55
55
  LABEL local
56
- LOCALBOOT -1
56
+ LOCALBOOT 0
57
57
  EOS
58
58
  end
59
59
 
@@ -3,11 +3,9 @@ module Stork
3
3
  class Base
4
4
  attr_reader :name
5
5
  attr_reader :options
6
- attr_reader :configuration
7
6
 
8
7
  def initialize(name = nil, options = {})
9
8
  @name = name
10
- @configuration = options[:configuration]
11
9
  @options = options
12
10
  setup
13
11
  end
@@ -2,13 +2,11 @@ module Stork
2
2
  module Resource
3
3
  class Delegator
4
4
  attr_reader :collection
5
- attr_reader :configuration
6
5
  attr_reader :delegated
7
6
 
8
7
  def initialize(inst, options)
9
8
  @delegated = inst
10
9
  @collection = options[:collection]
11
- @configuration = options[:configuration]
12
10
  end
13
11
 
14
12
  def get_collection_item(name, value)
@@ -15,6 +15,7 @@ module Stork
15
15
  attr_accessor :selinux
16
16
  attr_accessor :packages
17
17
  attr_accessor :run_list
18
+ attr_accessor :chef_environment
18
19
  attr_accessor :repos
19
20
  attr_accessor :stork
20
21
 
@@ -26,12 +27,13 @@ module Stork
26
27
  @chef = nil
27
28
  @pxemac = nil
28
29
  @selinux = 'enforcing'
29
- @stork = configuration ? configuration.server : 'localhost'
30
+ @stork = Configuration[:server]
30
31
 
31
32
  @pre_snippets = Array.new
32
33
  @post_snippets = Array.new
33
34
  @interfaces = Array.new
34
35
  @run_list = Array.new
36
+ @chef_environment = '_default'
35
37
  @repos = Array.new
36
38
  @packages = default_packages
37
39
 
@@ -41,24 +43,22 @@ module Stork
41
43
  end
42
44
 
43
45
  def hashify
44
- attrs = {}
45
- attrs['name'] = name
46
-
47
- attrs['distro'] = distro ? distro.name : ''
48
- attrs['template'] = template ? template.name : ''
49
- attrs['chef'] = chef ? chef.name : ''
50
-
51
- attrs['layout'] = layout.hashify
52
- attrs['interfaces'] = interfaces.map{|i| i.hashify}
53
- attrs['pre_snippets'] = pre_snippets.map{|s| s.name}
54
- attrs['post_snippets'] = post_snippets.map{|s| s.name}
55
-
56
- attrs['repos'] = repos.map{|r| r.name}
57
- attrs['run_list'] = run_list
58
- attrs['packages'] = packages
59
- attrs['timezone'] = timezone.zone
60
- attrs['selinux'] = selinux
61
- attrs
46
+ {
47
+ 'name' => name,
48
+ 'distro' => distro ? distro.name : '',
49
+ 'template' => template ? template.name : '',
50
+ 'chef' => chef ? chef.name : '',
51
+ 'layout' => layout.hashify,
52
+ 'interfaces' => interfaces.map{|i| i.hashify},
53
+ 'pre_snippets' => pre_snippets.map{|s| s.name},
54
+ 'post_snippets' => post_snippets.map{|s| s.name},
55
+ 'repos' => repos.map{|r| r.name},
56
+ 'run_list' => run_list,
57
+ 'chef_environment' => chef_environment,
58
+ 'packages' => packages,
59
+ 'timezone' => timezone.zone,
60
+ 'selinux' => selinux
61
+ }
62
62
  end
63
63
 
64
64
  def validate!
@@ -95,10 +95,6 @@ module Stork
95
95
  )
96
96
  end
97
97
 
98
- def deploy
99
- Stork::Deploy::Kickstart.new(self, configuration)
100
- end
101
-
102
98
  class HostDelegator < Stork::Resource::Delegator
103
99
  def layout(value, &block)
104
100
  if block_given?
@@ -187,6 +183,10 @@ module Stork
187
183
  delegated.run_list |= list
188
184
  end
189
185
 
186
+ def chef_environment(value)
187
+ delegated.chef_environment = value
188
+ end
189
+
190
190
  def repo(name, args = {})
191
191
  delegated.repos << Repo.build(name, args)
192
192
  end
@@ -12,7 +12,7 @@ module Stork
12
12
  @size = 1
13
13
  @type = 'ext4'
14
14
  @grow = false
15
- @recommended = true
15
+ @recommended = false
16
16
  @path = '/'
17
17
  end
18
18