zergrush 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,334 @@
1
+ require 'singleton'
2
+ require 'rubygems'
3
+
4
+ # GemPlugin is copyrighted free software by Zed A. Shaw
5
+ # <zedshaw at zedshaw dot com> You can redistribute it and/or modify it under
6
+ # either the terms of the GPL or the conditions below:
7
+
8
+ # 1. You may make and give away verbatim copies of the source form of the
9
+ # software without restriction, provided that you duplicate all of the
10
+ # original copyright notices and associated disclaimers.
11
+
12
+ # 2. You may modify your copy of the software in any way, provided that
13
+ # you do at least ONE of the following:
14
+
15
+ # a) place your modifications in the Public Domain or otherwise make them
16
+ # Freely Available, such as by posting said modifications to Usenet or an
17
+ # equivalent medium, or by allowing the author to include your
18
+ # modifications in the software.
19
+
20
+ # b) use the modified software only within your corporation or
21
+ # organization.
22
+
23
+ # c) rename any non-standard executables so the names do not conflict with
24
+ # standard executables, which must also be provided.
25
+
26
+ # d) make other distribution arrangements with the author.
27
+
28
+ # 3. You may distribute the software in object code or executable
29
+ # form, provided that you do at least ONE of the following:
30
+
31
+ # a) distribute the executables and library files of the software,
32
+ # together with instructions (in the manual page or equivalent) on where
33
+ # to get the original distribution.
34
+
35
+ # b) accompany the distribution with the machine-readable source of the
36
+ # software.
37
+
38
+ # c) give non-standard executables non-standard names, with
39
+ # instructions on where to get the original software distribution.
40
+
41
+ # d) make other distribution arrangements with the author.
42
+
43
+ # 4. You may modify and include the part of the software into any other
44
+ # software (possibly commercial). But some files in the distribution
45
+ # are not written by the author, so that they are not under this terms.
46
+
47
+ # 5. The scripts and library files supplied as input to or produced as
48
+ # output from the software do not automatically fall under the
49
+ # copyright of the software, but belong to whomever generated them,
50
+ # and may be sold commercially, and may be aggregated with this
51
+ # software.
52
+
53
+ # 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ # IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ # PURPOSE.
57
+
58
+ # Implements a dynamic plugin loading, configuration, and discovery system
59
+ # based on RubyGems and a simple additional name space that looks like a URI.
60
+ #
61
+ # A plugin is created and put into a category with the following code:
62
+ #
63
+ # class MyThing < GemPlugin::Plugin "/things"
64
+ # ...
65
+ # end
66
+ #
67
+ # What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager.
68
+ # You can then later get this plugin with GemPlugin::Manager.create("/things/mything")
69
+ # and can also pass in options as a second parameter.
70
+ #
71
+ # This isn't such a big deal, but the power is really from the GemPlugin::Manager.load
72
+ # method. This method will go through the installed gems and require_gem any
73
+ # that depend on the zergrush RubyGem. You can arbitrarily include or exclude
74
+ # gems based on what they also depend on, thus letting you load these gems when appropriate.
75
+ #
76
+ # Since this system was written originally for the Mongrel project that'll be the
77
+ # best example of using it.
78
+ #
79
+ # Imagine you have a neat plugin for Mongrel called snazzy_command that gives the
80
+ # mongrel_rails a new command snazzy (like: mongrel_rails snazzy). You'd like
81
+ # people to be able to grab this plugin if they want and use it, because it's snazzy.
82
+ #
83
+ # First thing you do is create a gem of your project and make sure that it depends
84
+ # on "mongrel" AND "zergrush". This signals to the GemPlugin system that this is
85
+ # a plugin for mongrel.
86
+ #
87
+ # Next you put this code into a file like lib/init.rb (can be anything really):
88
+ #
89
+ # class Snazzy < ZergGemPlugin::Plugin "/commands"
90
+ # ...
91
+ # end
92
+ #
93
+ # Then when you create your gem you have the following bits in your Rakefile:
94
+ #
95
+ # spec.add_dependency('mongrel', '>= 0.3.9')
96
+ # spec.add_dependency('zergrush', '>= 0.1')
97
+ # spec.autorequire = 'init.rb'
98
+ #
99
+ # Finally, you just have to now publish this gem for people to install and Mongrel
100
+ # will "magically" be able to install it.
101
+ #
102
+ # The "magic" part though is pretty simple and done via the GemPlugin::Manager.load
103
+ # method. Read that to see how it is really done.
104
+ module ZergGemPlugin
105
+
106
+ EXCLUDE = true
107
+ INCLUDE = false
108
+
109
+ class PluginNotLoaded < StandardError; end
110
+
111
+
112
+ # This class is used by people who use gem plugins (but don't necessarily make them)
113
+ # to add plugins to their own systems. It provides a way to load plugins, list them,
114
+ # and create them as needed.
115
+ #
116
+ # It is a singleton so you use like this: GemPlugins::Manager.instance.load
117
+ class Manager
118
+ include Singleton
119
+ attr_reader :plugins
120
+ attr_reader :gems
121
+
122
+
123
+ def initialize
124
+ # plugins that have been loaded
125
+ @plugins = {}
126
+
127
+ # keeps track of gems which have been loaded already by the manager *and*
128
+ # where they came from so that they can be referenced later
129
+ @gems = {}
130
+ end
131
+
132
+
133
+ # Responsible for going through the list of available gems and loading
134
+ # any plugins requested. It keeps track of what it's loaded already
135
+ # and won't load them again.
136
+ #
137
+ # It accepts one parameter which is a hash of gem depends that should include
138
+ # or exclude a gem from being loaded. A gem must depend on zergrush to be
139
+ # considered, but then each system has to add it's own INCLUDE to make sure
140
+ # that only plugins related to it are loaded.
141
+ #
142
+ # An example again comes from Mongrel. In order to load all Mongrel plugins:
143
+ #
144
+ # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE
145
+ #
146
+ # Which will load all plugins that depend on mongrel AND zergrush. Now, one
147
+ # extra thing we do is we delay loading Rails Mongrel plugins until after rails
148
+ # is configured. Do do this the mongrel_rails script has:
149
+ #
150
+ # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
151
+ # The only thing to remember is that this is saying "include a plugin if it
152
+ # depends on zergrush, mongrel, but NOT rails". If a plugin also depends on other
153
+ # stuff then it's loaded just fine. Only zergrush, mongrel, and rails are
154
+ # ever used to determine if it should be included.
155
+ #
156
+ # NOTE: Currently RubyGems will run autorequire on gems that get required AND
157
+ # on their dependencies. This really messes with people running edge rails
158
+ # since activerecord or other stuff gets loaded for just touching a gem plugin.
159
+ # To prevent this load requires the full path to the "init.rb" file, which
160
+ # avoids the RubyGems autorequire magic.
161
+ def load(needs = {})
162
+ needs = needs.merge({"zergrush" => INCLUDE})
163
+
164
+ Gem::Specification.each { |gem|
165
+ # don't load gems more than once
166
+ next if @gems.has_key? gem.name
167
+ check = needs.dup
168
+
169
+ # rolls through the depends and inverts anything it finds
170
+ gem.dependencies.each do |dep|
171
+ # this will fail if a gem is depended more than once
172
+ if check.has_key? dep.name
173
+ check[dep.name] = !check[dep.name]
174
+ end
175
+ end
176
+
177
+ # now since excluded gems start as true, inverting them
178
+ # makes them false so we'll skip this gem if any excludes are found
179
+ if (check.select {|name,test| !test}).length == 0
180
+ # looks like no needs were set to false, so it's good
181
+ if gem.metadata["zergrushplugin"] != nil
182
+ require File.join(gem.gem_dir, "lib", gem.name, "init.rb")
183
+ @gems[gem.name] = gem.gem_dir
184
+ end
185
+ end
186
+ }
187
+
188
+ return nil
189
+ end
190
+
191
+
192
+ # Not necessary for you to call directly, but this is
193
+ # how GemPlugin::Base.inherited actually adds a
194
+ # plugin to a category.
195
+ def register(category, name, klass)
196
+ @plugins[category] ||= {}
197
+ @plugins[category][name.downcase] = klass
198
+ end
199
+
200
+
201
+ # Resolves the given name (should include /category/name) to
202
+ # find the plugin class and create an instance. You can
203
+ # pass a second hash option that is then given to the Plugin
204
+ # to configure it.
205
+ def create(name, options = {})
206
+ last_slash = name.rindex("/")
207
+ category = name[0 ... last_slash]
208
+ plugin = name[last_slash .. -1]
209
+
210
+ map = @plugins[category]
211
+ if not map
212
+ raise "Plugin category #{category} does not exist"
213
+ elsif not map.has_key? plugin
214
+ raise "Plugin #{plugin} does not exist in category #{category}"
215
+ else
216
+ map[plugin].new(options)
217
+ end
218
+ end
219
+
220
+
221
+ # Simply says whether the given gem has been loaded yet or not.
222
+ def loaded?(gem_name)
223
+ @gems.has_key? gem_name
224
+ end
225
+
226
+
227
+ # GemPlugins can have a 'resources' directory which is packaged with them
228
+ # and can hold any data resources the plugin may need. The main problem
229
+ # is that it's difficult to figure out where these resources are
230
+ # actually located on the file system. The resource method tries to
231
+ # locate the real path for a given resource path.
232
+ #
233
+ # Let's say you have a 'resources/stylesheets/default.css' file in your
234
+ # gem distribution, then finding where this file really is involves:
235
+ #
236
+ # Manager.instance.resource("mygem", "/stylesheets/default.css")
237
+ #
238
+ # You either get back the full path to the resource or you get a nil
239
+ # if it doesn't exist.
240
+ #
241
+ # If you request a path for a GemPlugin that hasn't been loaded yet
242
+ # then it will throw an PluginNotLoaded exception. The gem may be
243
+ # present on your system in this case, but you just haven't loaded
244
+ # it with Manager.instance.load properly.
245
+ def resource(gem_name, path)
246
+ if not loaded? gem_name
247
+ raise PluginNotLoaded.new("Plugin #{gem_name} not loaded when getting resource #{path}")
248
+ end
249
+
250
+ file = File.join(@gems[gem_name], "resources", path)
251
+
252
+ if File.exist? file
253
+ return file
254
+ else
255
+ return nil
256
+ end
257
+ end
258
+
259
+
260
+ # While Manager.resource will find arbitrary resources, a special
261
+ # case is when you need to load a set of configuration defaults.
262
+ # GemPlugin normalizes this to be if you have a file "resources/defaults.yaml"
263
+ # then you'll be able to load them via Manager.config.
264
+ #
265
+ # How you use the method is you get the options the user wants set, pass
266
+ # them to Manager.instance.config, and what you get back is a new Hash
267
+ # with the user's settings overriding the defaults.
268
+ #
269
+ # opts = Manager.instance.config "mygem", :age => 12, :max_load => .9
270
+ #
271
+ # In the above case, if defaults had {:age => 14} then it would be
272
+ # changed to 12.
273
+ #
274
+ # This loads the .yaml file on the fly every time, so doing it a
275
+ # whole lot is very stupid. If you need to make frequent calls to
276
+ # this, call it once with no options (Manager.instance.config) then
277
+ # use the returned defaults directly from then on.
278
+ def config(gem_name, options={})
279
+ config_file = Manager.instance.resource(gem_name, "/defaults.yaml")
280
+ if config_file
281
+ begin
282
+ defaults = YAML.load_file(config_file)
283
+ return defaults.merge(options)
284
+ rescue
285
+ raise "Error loading config #{config_file} for gem #{gem_name}"
286
+ end
287
+ else
288
+ return options
289
+ end
290
+ end
291
+ end
292
+
293
+ # This base class for plugins really does nothing
294
+ # more than wire up the new class into the right category.
295
+ # It is not thread-safe yet but will be soon.
296
+ class Base
297
+
298
+ attr_reader :options
299
+
300
+
301
+ # See Mongrel::Plugin for an explanation.
302
+ def Base.inherited(klass)
303
+ name = "/" + klass.to_s.downcase
304
+ Manager.instance.register(@@category, name, klass)
305
+ @@category = nil
306
+ end
307
+
308
+ # See Mongrel::Plugin for an explanation.
309
+ def Base.category=(category)
310
+ @@category = category
311
+ end
312
+
313
+ def initialize(options = {})
314
+ @options = options
315
+ end
316
+ end
317
+
318
+ # This nifty function works with the GemPlugin::Base to give you
319
+ # the syntax:
320
+ #
321
+ # class MyThing < GemPlugin::Plugin "/things"
322
+ # ...
323
+ # end
324
+ #
325
+ # What it does is temporarily sets the GemPlugin::Base.category, and then
326
+ # returns GemPlugin::Base. Since the next immediate thing Ruby does is
327
+ # use this returned class to create the new class, GemPlugin::Base.inherited
328
+ # gets called. GemPlugin::Base.inherited then uses the set category, class name,
329
+ # and class to register the plugin in the right way.
330
+ def ZergGemPlugin::Plugin(c)
331
+ Base.category = c
332
+ Base
333
+ end
334
+ end
data/lib/zerg/hive.rb CHANGED
@@ -27,11 +27,12 @@ require 'json-schema'
27
27
  require 'fileutils'
28
28
  require 'singleton'
29
29
  require 'highline/import'
30
+ require_relative 'erbalize'
30
31
 
31
32
  module Zerg
32
33
  class Hive
33
34
  include Singleton
34
- attr_reader :hive
35
+ attr_reader :hive, :load_path
35
36
 
36
37
  def loaded
37
38
  @loaded || false
@@ -42,12 +43,12 @@ module Zerg
42
43
  return
43
44
  end
44
45
 
45
- load_path = (ENV['HIVE_CWD'] == nil) ? File.join("#{Dir.pwd}", ".hive") : File.join("#{ENV['HIVE_CWD']}", ".hive")
46
- abort("ERROR: '.hive' not found at #{load_path}. Run 'zerg init', change HIVE_CWD or run zerg from a different path.") unless File.directory?(load_path)
46
+ @load_path = (ENV['HIVE_CWD'] == nil) ? File.join("#{Dir.pwd}", ".hive") : File.join("#{ENV['HIVE_CWD']}", ".hive")
47
+ abort("ERROR: '.hive' not found at #{@load_path}. Run 'zerg init', change HIVE_CWD or run zerg from a different path.") unless File.directory?(@load_path)
47
48
 
48
49
  # load all .ke files into one big hash
49
50
  @hive = Hash.new
50
- Dir.glob(File.join("#{load_path}", "*.ke")) do |ke_file|
51
+ Dir.glob(File.join("#{@load_path}", "*.ke")) do |ke_file|
51
52
  # do work on files ending in .rb in the desired directory
52
53
  begin
53
54
  ke_file_hash = JSON.parse( IO.read(ke_file) )
@@ -82,20 +83,32 @@ module Zerg
82
83
  end
83
84
 
84
85
  def self.verify
85
- load_path = (ENV['HIVE_CWD'] == nil) ? File.join("#{Dir.pwd}", ".hive") : File.join("#{ENV['HIVE_CWD']}", ".hive")
86
- abort("ERROR: '.hive' not found at #{load_path}. Run 'zerg init', change HIVE_CWD or run zerg from a different path.") unless File.directory?(load_path)
86
+ instance.load
87
87
 
88
- Dir.glob(File.join("#{load_path}", "*.ke")) do |ke_file|
88
+ Dir.glob(File.join("#{instance.load_path}", "*.ke")) do |ke_file|
89
89
  begin
90
- ke_file_hash = JSON.parse( IO.read(ke_file) )
90
+ ke_file_hash = JSON.parse( File.open(ke_file, 'r').read )
91
91
 
92
92
  # verify against schema.
93
- errors = JSON::Validator.fully_validate(File.join("#{File.dirname(__FILE__)}", "../../data/ke.schema"), ke_file_hash, :errors_as_objects => true)
94
- unless errors.empty?
95
- abort("ERROR: #{ke_file} failed validation. Errors: #{errors.ai}")
96
- end
93
+ # first get the tasks schema piece from the driver
94
+ pmgr = ZergGemPlugin::Manager.instance
95
+ pmgr.load
96
+ abort("ERROR: 'drivertype' is missing from #{ke_file}") unless ke_file_hash["vm"]["driver"]["drivertype"] != nil
97
+ driver = pmgr.create("/driver/#{ke_file_hash["vm"]["driver"]["drivertype"]}")
98
+ driver_schema = driver.task_schema
99
+
100
+ schema_template = File.open(File.join("#{File.dirname(__FILE__)}", "..", "..", "data", "ke.schema"), 'r').read
101
+ sources = {
102
+ :driver_tasks_schema => driver_schema
103
+ }
104
+ full_schema = JSON.parse(Erbalize.erbalize_hash(schema_template, sources))
105
+
106
+ errors = JSON::Validator.fully_validate(full_schema, ke_file_hash, :errors_as_objects => true)
107
+ abort("ERROR: #{ke_file} failed validation. Errors: #{errors.ai}") unless errors.empty?
97
108
  rescue JSON::ParserError => err
98
109
  abort("ERROR: Could not parse #{ke_file}. Likely invalid JSON.")
110
+ rescue ZergGemPlugin::PluginNotLoaded
111
+ abort("ERROR: driver #{ke_file_hash["vm"]["driver"]["drivertype"]} not found. Did you install the plugin gem?")
99
112
  end
100
113
  end
101
114
 
@@ -103,18 +116,32 @@ module Zerg
103
116
  end
104
117
 
105
118
  def self.import(file, force)
106
- load_path = (ENV['HIVE_CWD'] == nil) ? File.join("#{Dir.pwd}", ".hive") : File.join("#{ENV['HIVE_CWD']}", ".hive")
107
- abort("ERROR: '.hive' not found at #{load_path}. Run 'zerg init', change HIVE_CWD or run zerg from a different path.") unless File.directory?(load_path)
119
+ instance.load
108
120
  abort("ERROR: '#{file}' not found!") unless File.exist?(file)
109
- abort("ERROR: '#{File.basename(file)}' already exists in hive!") unless !File.exist?(File.join(load_path, File.basename(file))) || force == true
121
+ abort("ERROR: '#{File.basename(file)}' already exists in hive!") unless !File.exist?(File.join(instance.load_path, File.basename(file))) || force == true
110
122
 
111
123
  # check the file against schema.
112
124
  begin
113
- ke_file_hash = JSON.parse( IO.read(file) )
114
- errors = JSON::Validator.fully_validate(File.join("#{File.dirname(__FILE__)}", "../../data/ke.schema"), ke_file_hash, :errors_as_objects => true)
115
- abort("ERROR: #{file} failed validation. Errors: #{errors.ai}") unless errors.empty?
116
125
 
117
- FileUtils.cp(file, File.join(load_path, File.basename(file)))
126
+ ke_file_hash = JSON.parse( File.open(file, 'r').read )
127
+
128
+ # get the tasks schema piece from the driver
129
+ pmgr = ZergGemPlugin::Manager.instance
130
+ pmgr.load
131
+ abort("ERROR: 'drivertype' is missing from #{ke_file}") unless ke_file_hash["vm"]["driver"]["drivertype"] != nil
132
+ driver = pmgr.create("/driver/#{ke_file_hash["vm"]["driver"]["drivertype"]}")
133
+ driver_schema = driver.task_schema
134
+
135
+ schema_template = File.open(File.join("#{File.dirname(__FILE__)}", "..", "..", "data", "ke.schema"), 'r').read
136
+ sources = {
137
+ :driver_tasks_schema => driver_schema
138
+ }
139
+ full_schema = JSON.parse(Erbalize.erbalize_hash(schema_template, sources))
140
+
141
+ errors = JSON::Validator.fully_validate(full_schema, ke_file_hash, :errors_as_objects => true)
142
+ abort("ERROR: #{ke_file} failed validation. Errors: #{errors.ai}") unless errors.empty?
143
+
144
+ FileUtils.cp(file, File.join(instance.load_path, File.basename(file)))
118
145
  rescue JSON::ParserError => err
119
146
  abort("ERROR: Could not parse #{file}. Likely invalid JSON.")
120
147
  end
@@ -122,12 +149,11 @@ module Zerg
122
149
  end
123
150
 
124
151
  def self.remove(taskname, force)
125
- load_path = (ENV['HIVE_CWD'] == nil) ? File.join("#{Dir.pwd}", ".hive") : File.join("#{ENV['HIVE_CWD']}", ".hive")
126
- abort("ERROR: '.hive' not found at #{load_path}. Run 'zerg init', change HIVE_CWD or run zerg from a different path.") unless File.directory?(load_path)
127
- abort("ERROR: '#{taskname}' not found!") unless File.exist?(File.join(load_path, "#{taskname}.ke"))
152
+ instance.load
153
+ abort("ERROR: '#{taskname}' not found!") unless File.exist?(File.join(instance.load_path, "#{taskname}.ke"))
128
154
 
129
155
  # check the file against schema.
130
- taskfile = File.join(load_path, "#{taskname}.ke")
156
+ taskfile = File.join(instance.load_path, "#{taskname}.ke")
131
157
 
132
158
  agreed = true
133
159
  if force != true
@@ -136,8 +162,8 @@ module Zerg
136
162
 
137
163
  abort("Cancelled!") unless agreed == true
138
164
 
139
- FileUtils.rm_rf(File.join(load_path, "driver", taskname))
140
- FileUtils.rm(File.join(load_path, "#{taskname}.ke"))
165
+ FileUtils.rm_rf(File.join(instance.load_path, "driver", taskname))
166
+ FileUtils.rm(File.join(instance.load_path, "#{taskname}.ke"))
141
167
 
142
168
  puts "SUCCESS!"
143
169
  end