linecook 1.2.1 → 2.0.0

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 (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