linecook 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/{History → History.rdoc} +3 -2
  2. data/README.rdoc +93 -0
  3. data/bin/linecook +32 -56
  4. data/bin/linecook_run +19 -6
  5. data/bin/linecook_scp +12 -4
  6. data/doc/vm_setup.rdoc +75 -0
  7. data/lib/linecook.rb +3 -2
  8. data/lib/linecook/attributes.rb +33 -8
  9. data/lib/linecook/command.rb +61 -0
  10. data/lib/linecook/command_set.rb +85 -0
  11. data/lib/linecook/command_utils.rb +20 -0
  12. data/lib/linecook/commands/build.rb +108 -57
  13. data/lib/linecook/commands/compile.rb +181 -0
  14. data/lib/linecook/commands/{helper.rb → compile_helper.rb} +123 -94
  15. data/lib/linecook/commands/run.rb +43 -39
  16. data/lib/linecook/commands/snapshot.rb +24 -24
  17. data/lib/linecook/commands/ssh.rb +7 -7
  18. data/lib/linecook/commands/start.rb +10 -10
  19. data/lib/linecook/commands/state.rb +7 -7
  20. data/lib/linecook/commands/stop.rb +3 -3
  21. data/lib/linecook/commands/{vbox_command.rb → virtual_box_command.rb} +31 -29
  22. data/lib/linecook/cookbook.rb +149 -131
  23. data/lib/linecook/executable.rb +28 -0
  24. data/lib/linecook/package.rb +177 -361
  25. data/lib/linecook/proxy.rb +4 -10
  26. data/lib/linecook/recipe.rb +289 -369
  27. data/lib/linecook/test.rb +114 -98
  28. data/lib/linecook/utils.rb +31 -41
  29. data/lib/linecook/version.rb +2 -6
  30. metadata +120 -68
  31. data/HowTo/Control Virtual Machines +0 -106
  32. data/HowTo/Generate Scripts +0 -268
  33. data/HowTo/Run Scripts +0 -87
  34. data/HowTo/Setup Virtual Machines +0 -76
  35. data/README +0 -117
  36. data/lib/linecook/commands.rb +0 -11
  37. data/lib/linecook/commands/command.rb +0 -58
  38. data/lib/linecook/commands/command_error.rb +0 -12
  39. data/lib/linecook/commands/env.rb +0 -89
  40. data/lib/linecook/commands/init.rb +0 -86
  41. data/lib/linecook/commands/package.rb +0 -57
  42. data/lib/linecook/template.rb +0 -17
  43. data/lib/linecook/test/command_parser.rb +0 -75
  44. data/lib/linecook/test/file_test.rb +0 -197
  45. data/lib/linecook/test/regexp_escape.rb +0 -86
  46. data/lib/linecook/test/shell_test.rb +0 -177
  47. data/lib/linecook/test/shim.rb +0 -71
  48. data/templates/Gemfile +0 -3
  49. data/templates/Rakefile +0 -146
  50. data/templates/_gitignore +0 -4
  51. data/templates/attributes/project_name.rb +0 -3
  52. data/templates/config/ssh +0 -14
  53. data/templates/cookbook +0 -10
  54. data/templates/files/example.txt +0 -1
  55. data/templates/helpers/project_name/echo.erb +0 -4
  56. data/templates/packages/abox.yml +0 -2
  57. data/templates/project_name.gemspec +0 -30
  58. data/templates/recipes/abox.rb +0 -16
  59. data/templates/templates/example.erb +0 -1
  60. data/templates/test/project_name_test.rb +0 -24
  61. data/templates/test/test_helper.rb +0 -14
@@ -0,0 +1,28 @@
1
+ require 'linecook/command_set'
2
+ require 'linecook/commands/build'
3
+ require 'linecook/commands/start'
4
+ require 'linecook/commands/stop'
5
+ require 'linecook/commands/state'
6
+ require 'linecook/commands/snapshot'
7
+ require 'linecook/commands/ssh'
8
+ require 'linecook/commands/run'
9
+
10
+ module Linecook
11
+ class Executable < Linecook::CommandSet
12
+ class << self
13
+ def commands
14
+ {
15
+ 'build' => Linecook::Commands::Build,
16
+ 'compile' => Linecook::Commands::Compile,
17
+ 'compile-helper' => Linecook::Commands::CompileHelper,
18
+ 'start' => Linecook::Commands::Start,
19
+ 'stop' => Linecook::Commands::Stop,
20
+ 'state' => Linecook::Commands::State,
21
+ 'snapshot' => Linecook::Commands::Snapshot,
22
+ 'ssh' => Linecook::Commands::Ssh,
23
+ 'run' => Linecook::Commands::Run
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,396 +1,212 @@
1
- require 'linecook/cookbook'
2
- require 'linecook/recipe'
3
- require 'linecook/template'
4
1
  require 'tempfile'
2
+ require 'stringio'
5
3
 
6
4
  module Linecook
7
5
  class Package
8
- class << self
9
- def setup(env, cookbook=nil)
10
- unless env.kind_of?(Hash)
11
- env = Utils.load_config(env)
12
- end
13
-
14
- package = new(env)
15
-
16
- if cookbook
17
- manifest = package.manifest
18
- manifest.replace cookbook.manifest
19
- end
20
-
21
- package
22
- end
23
-
24
- def init(package_file=nil, project_dir=nil)
25
- cookbook = Cookbook.init(project_dir)
26
- package = setup(package_file, cookbook)
27
-
28
- if package_file
29
- package.context[PACKAGE_KEY] ||= begin
30
- name = File.basename(package_file).chomp(File.extname(package_file))
31
- {'recipes' => { 'run' => name }}
32
- end
33
- end
34
-
35
- package
36
- end
37
- end
38
-
39
- CONTEXT_KEY = 'linecook'
40
- COOKBOOK_KEY = 'cookbook'
41
- MANIFEST_KEY = 'manifest'
42
- PACKAGE_KEY = 'package'
43
- REGISTRY_KEY = 'registry'
44
-
45
- FILES_KEY = 'files'
46
- TEMPLATES_KEY = 'templates'
47
- RECIPES_KEY = 'recipes'
48
-
49
6
  # The package environment
50
7
  attr_reader :env
51
-
52
- # An array of tempfiles generated by self (used to cleanup on close)
53
- attr_reader :tempfiles
54
-
55
- # A hash of counters used by variable.
56
- attr_reader :counters
57
-
58
- # A hash of callbacks registered with self
59
- attr_reader :callbacks
60
-
8
+
9
+ # A registry of (path, source_path) pairs recording what files are
10
+ # included in the package.
11
+ attr_reader :registry
12
+
13
+ # A hash of (path, Hash) pairs identifing export options for a package
14
+ # file. See on_export.
15
+ attr_reader :export_options
16
+
17
+ # A hash of default export options.
18
+ attr_reader :default_export_options
19
+
61
20
  def initialize(env={})
62
21
  @env = env
63
- @tempfiles = []
64
- @counters = Hash.new(0)
65
- @callbacks = Hash.new {|hash, key| hash[key] = StringIO.new }
66
- end
67
-
68
- # Returns the linecook context in env, as keyed by CONTEXT_KEY. Defaults
69
- # to an empty hash.
70
- def context
71
- env[CONTEXT_KEY] ||= {}
22
+ @registry = {}
23
+ @export_options = {}
24
+ @default_export_options = {}
72
25
  end
73
-
74
- # Returns the manifest in config, as keyed by MANIFEST_KEY. Defaults to an
75
- # empty hash.
76
- def manifest
77
- context[MANIFEST_KEY] ||= {}
78
- end
79
-
80
- # Returns the registry in config, as keyed by REGISTRY_KEY. Defaults to an
81
- # empty hash. A hash of (target_name, source_path) pairs identifying
82
- # files that should be included in a package
83
- def registry
84
- context[REGISTRY_KEY] ||= {}
85
- end
86
-
87
- # Returns the linecook configs in env, as keyed by CONFIG_KEY. Defaults
88
- # to an empty hash.
89
- def config
90
- context[PACKAGE_KEY] ||= {}
91
- end
92
-
93
- # Returns the hash of (source, target) pairs identifying which of the
94
- # files will be built into self by build. Files are identified by
95
- # FILES_KEY in config, and normalized the same way as recipes.
96
- def files
97
- config[FILES_KEY] = Utils.hashify(config[FILES_KEY])
98
- end
99
-
100
- # Returns the hash of (source, target) pairs identifying which templates
101
- # will be built into self by build. Templates are identified by
102
- # TEMPLATES_KEY in config, and normalized the same way as recipes.
103
- def templates
104
- config[TEMPLATES_KEY] = Utils.hashify(config[TEMPLATES_KEY])
105
- end
106
-
107
- # Returns the hash of (source, target) pairs identifying which recipes
108
- # will be built into self by build. Recipes are identified by RECIPES_KEY
109
- # in config.
110
- #
111
- # Non-hash recipes are normalized by expanding arrays into a redundant
112
- # hash, such that each entry has the same source and target (more
113
- # concretely, the 'example' recipe is registered as the 'example' script).
114
- # Strings are split along colons into an array and then expanded.
115
- #
116
- # For example:
117
- #
118
- # package = Package.new('linecook' => {'package' => {'recipes' => 'a:b:c'}})
119
- # package.recipes # => {'a' => 'a', 'b' => 'b', 'c' => 'c'}
26
+
27
+ # Registers a file into the package with the specified export options. The
28
+ # source path should be the path to a file or directory to include. To
29
+ # make an empty file or directory use :file or :dir as the source_path.
120
30
  #
121
- def recipes
122
- config[RECIPES_KEY] = Utils.hashify(config[RECIPES_KEY])
123
- end
124
-
125
- # Registers the source_path to target_name in the registry and
126
- # revese_registry. Raises an error if the source_path is already
127
- # registered.
128
- def register(target_name, source_path, mode=0600)
129
- source_path = File.expand_path(source_path)
130
-
131
- if registry.has_key?(target_name) && registry[target_name] != [source_path, mode]
132
- raise "already registered: #{target_name} (%s, %o)" % registry[target_name]
133
- end
134
-
135
- registry[target_name] = [source_path, mode]
136
- target_name
137
- end
138
-
139
- # Increments target_name until an unregistered name is found and returns
140
- # the result.
141
- def next_target_name(target_name='file')
142
- count = 0
143
- registry.each_key do |key|
144
- if key.index(target_name) == 0
145
- count += 1
146
- end
31
+ # Raises an error if a source is already registered at the path.
32
+ def register(path, source_path, options={})
33
+ package_path = path.to_s.strip
34
+
35
+ if package_path.empty?
36
+ raise "invalid package path: #{path.inspect}"
147
37
  end
148
-
149
- if count > 0
150
- target_name = "#{target_name}.#{count}"
38
+
39
+ if registry.has_key?(package_path)
40
+ raise "already registered: #{path.inspect}"
151
41
  end
152
-
153
- target_name
154
- end
155
-
156
- # Returns a package-unique variable with base 'name'.
157
- def next_variable_name(context)
158
- context = context.to_s
159
-
160
- count = counters[context]
161
- counters[context] += 1
162
-
163
- "#{context}#{count}"
164
- end
165
-
166
- # Returns true if there is a path for the specified resource in manifest.
167
- def resource?(type, path)
168
- resources = manifest[type]
169
- resources && resources.has_key?(path)
170
- end
171
-
172
- # Returns the path to the resource in manfiest. Raises an error if there
173
- # is no such resource.
174
- def resource_path(type, path)
175
- resources = manifest[type]
176
- resource_path = resources ? resources[path] : nil
177
- resource_path or raise "no such resource in manifest: #{type.inspect} #{path.inspect}"
178
- end
179
-
180
- # Returns the resource_path the named attributes file (ex 'attributes/name.rb').
181
- def attributes_path(attributes_name)
182
- resource_path('attributes', attributes_name)
183
- end
184
-
185
- # Returns the resource_path the named file (ex 'files/name')
186
- def file_path(file_name)
187
- resource_path('files', file_name)
188
- end
189
-
190
- # Returns the resource_path the named template file (ex 'templates/name.erb').
191
- def template_path(template_name)
192
- resource_path('templates', template_name)
42
+
43
+ source_path = resolve_source_path(source_path)
44
+ registry[package_path] = source_path
45
+ on_export(package_path, options)
46
+
47
+ source_path
193
48
  end
194
-
195
- # Returns the resource_path the named recipe file (ex 'recipes/name.rb').
196
- def recipe_path(recipe_name)
197
- resource_path('recipes', recipe_name)
49
+
50
+ # Removes a file from the package. Returns the source path if one was
51
+ # registered.
52
+ def unregister(path)
53
+ registry.delete(path)
54
+ export_options.delete(path)
198
55
  end
199
-
200
- # Loads the named attributes file into and returns an instance of
201
- # Attributes. The loading mechanism depends on the attributes file
202
- # extname.
56
+
57
+ # Sets export options for the package file. Available options (as
58
+ # symbols):
203
59
  #
204
- # .rb: evaluate in the context of attributes
205
- # .yml,.yaml,.json: load as YAML and merge into attributes
60
+ # move:: When set to true the source will be moved into place
61
+ # rather than copied (the default)
62
+ # mode:: Sets the mode of the package file
206
63
  #
207
- # All other file types raise an error. Simply returns a new Attributes
208
- # instance if no name is given.
209
- def load_attributes(attributes_name=nil)
210
- attributes = Attributes.new
211
-
212
- if attributes_name
213
- path = attributes_path(attributes_name)
214
-
215
- case File.extname(path)
216
- when '.rb'
217
- attributes.instance_eval(File.read(path), path)
218
- when '.yml', '.yaml', '.json'
219
- attributes.attrs.merge!(YAML.load_file(path))
220
- else
221
- raise "invalid attributes format: #{path.inspect}"
64
+ # Unless specified, the values in default_export_options will be used.
65
+ def on_export(path, options={})
66
+ export_options[path] = default_export_options.merge(options)
67
+ end
68
+
69
+ # Generates a tempfile and registers it into the package at the specified
70
+ # path. Returns the open tempfile.
71
+ def add(path, options={})
72
+ options = {
73
+ :move => true
74
+ }.merge(options)
75
+
76
+ # preserve a reference to tempfile in options so that it will not be
77
+ # unlinked before it can be moved into the package during export
78
+ tempfile = Tempfile.new File.basename(path)
79
+ options[:tempfile] = tempfile
80
+
81
+ if block_given?
82
+ begin
83
+ yield tempfile
84
+ ensure
85
+ tempfile.close
222
86
  end
223
87
  end
224
-
225
- attributes
226
- end
227
-
228
- # Load the template file with the specified name and wraps as a Template.
229
- # Returns the new Template object.
230
- def load_template(template_name)
231
- Template.new template_path(template_name)
232
- end
233
-
234
- # Loads and returns the helper constant specified by helper_name. The
235
- # helper_name is underscored to determine a require path and camelized to
236
- # determine the constant name.
237
- def load_helper(helper_name)
238
- require Utils.underscore(helper_name)
239
- Utils.constantize(helper_name)
240
- end
241
-
242
- # Returns a recipe bound to self.
243
- def setup_recipe(target_name = next_target_name, mode=0700)
244
- Recipe.new(self, target_name, mode)
245
- end
246
-
247
- # Generates a tempfile for the target path and registers it with self. As
248
- # with register, the target_name will be incremented as needed. Returns
249
- # the open tempfile.
250
- def setup_tempfile(target_name = next_target_name, mode=0600)
251
- tempfile = Tempfile.new File.basename(target_name)
252
-
253
- register(target_name, tempfile.path, mode)
254
- tempfiles << tempfile
255
-
88
+
89
+ register path, tempfile.path, options
256
90
  tempfile
257
91
  end
258
-
259
- # Returns true if the source_path is for a tempfile generated by self.
260
- def tempfile?(source_path)
261
- tempfiles.any? {|tempfile| tempfile.path == source_path }
92
+
93
+ # Adds an empty dir at path. Returns nil.
94
+ def add_dir(path, options={})
95
+ register path, :dir, options
262
96
  end
263
-
264
- # Looks up the file with the specified name using file_path and registers
265
- # it to target_name. Raises an error if the target is already registered.
266
- def build_file(target_name, file_name=target_name, mode=0600)
267
- register target_name, file_path(file_name), mode
268
- self
97
+
98
+ alias rm unregister
99
+
100
+ # Returns the source path registered at the path, or nil if no source is
101
+ # registered.
102
+ def source_path(path)
103
+ registry[path]
269
104
  end
270
-
271
- # Looks up the template with the specified name using template_path,
272
- # builds, and registers it to target_name. The locals will be set for
273
- # access in the template context. Raises an error if the target is
274
- # already registered. Returns self.
275
- def build_template(target_name, template_name=target_name, mode=0600, locals={'attrs' => env})
276
- content = load_template(template_name).build(locals)
277
-
278
- target = setup_tempfile(target_name, mode)
279
- target << content
280
- target.close
281
- self
105
+
106
+ # Returns an array of paths that the source path is registered to.
107
+ def paths(source_path)
108
+ source = resolve_source_path(source_path)
109
+
110
+ paths = []
111
+ registry.each_pair do |path, current|
112
+ if current == source
113
+ paths << path
114
+ end
115
+ end
116
+ paths
282
117
  end
283
-
284
- # Looks up the recipe with the specified name using recipe_path, evaluates
285
- # it, and registers the result to target_name. Raises an error if the
286
- # target is already registered. Returns self.
287
- def build_recipe(target_name, recipe_name=target_name, mode=0700)
288
- path = recipe_path(recipe_name)
289
- recipe = setup_recipe(target_name, mode)
290
- recipe.instance_eval(File.read(path), path)
291
- recipe.close
292
-
293
- self
118
+
119
+ # Returns the content to be added to the package at the path. Returns nil
120
+ # if nothing is registered.
121
+ def content(path, length=nil, offset=nil)
122
+ source = source_path(path)
123
+ source ? File.read(source, length, offset) : nil
294
124
  end
295
-
296
- # Builds the files, templates, and recipes for self. Returns self.
297
- def build
298
- files.each do |target_name, file_name|
299
- build_file(target_name, *file_name)
300
- end
301
-
302
- templates.each do |target_name, template_name|
303
- build_template(target_name, *template_name)
125
+
126
+ # Increments path until an unregistered path is found and returns the
127
+ # result in the format "path.count".
128
+ def next_path(path='file')
129
+ count = 0
130
+ registry.each_key do |current|
131
+ if current.index(path) == 0
132
+ count += 1
133
+ end
304
134
  end
305
-
306
- recipes.each do |target_name, recipe_name|
307
- build_recipe(target_name, *recipe_name)
135
+
136
+ if count > 0
137
+ path = "#{path}.#{count}"
308
138
  end
309
-
310
- self
311
- end
312
-
313
- # Returns the content of the source_path for target_name, as registered in
314
- # self. Returns nil if the target is not registered.
315
- def content(target_name, length=nil, offset=nil)
316
- path = source_path(target_name)
317
- path ? File.read(path, length, offset) : nil
318
- end
319
-
320
- # Returns the source_path for target_name, as registered in self. Returns
321
- # nil if the target is not registered.
322
- def source_path(target_name)
323
- entry = registry[target_name]
324
- entry ? entry[0] : nil
325
- end
326
-
327
- # Returns the mode for target_name, as registered in self. Returns nil if
328
- # the target is not registered.
329
- def mode(target_name)
330
- entry = registry[target_name]
331
- entry ? entry[1] : nil
139
+
140
+ path
332
141
  end
333
-
334
- # Closes all tempfiles and returns self.
335
- def close
336
- tempfiles.each do |tempfile|
337
- tempfile.close unless tempfile.closed?
142
+
143
+ def export(dir)
144
+ registry.keys.sort.each do |path|
145
+ target_path = File.join(dir, path)
146
+ source_path = registry[path]
147
+ options = export_options[path] || default_export_options
148
+
149
+ if source_path != target_path
150
+ if File.exists?(target_path)
151
+ if block_given?
152
+ unless yield(source_path, target_path)
153
+ next
154
+ end
155
+ else
156
+ raise "already exists: #{target_path.inspect}"
157
+ end
158
+ end
159
+
160
+ target_dir = File.dirname(target_path)
161
+ FileUtils.mkdir_p(target_dir)
162
+
163
+ case source_path
164
+ when :file
165
+ FileUtils.touch target_path
166
+ when :dir
167
+ FileUtils.mkdir target_path
168
+ else
169
+ if File.directory?(source_path)
170
+ export_dir(source_path, target_path, options)
171
+ else
172
+ export_file(source_path, target_path, options)
173
+ end
174
+ end
175
+ end
176
+
177
+ if mode = options[:mode]
178
+ FileUtils.chmod(mode, target_path)
179
+ end
180
+
181
+ registry[path] = target_path
338
182
  end
339
- self
183
+
184
+ registry
340
185
  end
341
-
342
- # Closes and clears all tempfiles, the registry, callbacks, and counters.
343
- def reset
344
- close
345
- registry.clear
346
- tempfiles.clear
347
- callbacks.clear
348
- counters.clear
349
- self
186
+
187
+ private
188
+
189
+ def resolve_source_path(source_path) # :nodoc:
190
+ case source_path
191
+ when :file, :dir then source_path
192
+ else File.expand_path(source_path.to_s)
193
+ end
350
194
  end
351
-
352
- # Closes self and exports the registry to dir by copying or moving the
353
- # registered source paths to the target path under dir. By default
354
- # tempfiles are moved while all other files are copied.
355
- #
356
- # Returns registry, which is re-written to reflect the new source paths.
357
- def export(dir, options={})
358
- close
359
-
360
- options = {
361
- :allow_move => true
362
- }.merge(options)
363
-
364
- allow_move = options[:allow_move]
365
-
366
- if File.exists?(dir)
367
- FileUtils.rm_r(dir)
195
+
196
+ def export_dir(source_path, target_path, options) # :nodoc:
197
+ if options[:move]
198
+ FileUtils.mv(source_path, target_path)
199
+ else
200
+ FileUtils.cp_r(source_path, target_path)
368
201
  end
369
-
370
- registry.each_key do |target_name|
371
- export_path = File.join(dir, target_name)
372
- export_dir = File.dirname(export_path)
373
- source_path, mode = registry[target_name]
374
-
375
- next if source_path == export_path
376
-
377
- unless File.exists?(export_dir)
378
- FileUtils.mkdir_p(export_dir)
379
- end
380
-
381
- if allow_move && tempfile?(source_path)
382
- FileUtils.mv(source_path, export_path)
383
- else
384
- FileUtils.cp(source_path, export_path)
385
- end
386
-
387
- FileUtils.chmod(mode, export_path)
388
-
389
- registry[target_name] = [export_path, mode]
202
+ end
203
+
204
+ def export_file(source_path, target_path, options) # :nodoc:
205
+ if options[:move]
206
+ FileUtils.mv(source_path, target_path)
207
+ else
208
+ FileUtils.cp(source_path, target_path)
390
209
  end
391
-
392
- tempfiles.clear
393
- registry
394
210
  end
395
211
  end
396
212
  end