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,61 @@
1
+ require 'configurable'
2
+
3
+ module Linecook
4
+ class Command
5
+ class << self
6
+ def parse(argv=ARGV)
7
+ parser = configs.to_parser(:add_defaults => false)
8
+ yield(parser) if block_given?
9
+ parser.sort_opts!
10
+ parser.parse!(argv)
11
+
12
+ new(parser.config)
13
+ end
14
+
15
+ def signature
16
+ arguments = args.arguments.collect do |arg|
17
+ arg = arg.upcase
18
+ arg[0] == ?* ? "[#{arg[1..-1]}]" : arg
19
+ end
20
+ arguments.pop if arguments.last.to_s[0] == ?&
21
+
22
+ "[options] #{arguments.join(' ')}"
23
+ end
24
+
25
+ # Returns a help string that formats the desc documentation.
26
+ def help
27
+ lines = desc.kind_of?(Lazydoc::Comment) ? desc.wrap(78, 2, nil) : []
28
+
29
+ unless lines.empty?
30
+ line = '-' * 80
31
+ lines.unshift(line)
32
+ lines.push(line)
33
+ end
34
+
35
+ lines.join("\n")
36
+ end
37
+ end
38
+
39
+ extend Lazydoc::Attributes
40
+ include Configurable
41
+
42
+ lazy_attr :desc
43
+ lazy_attr :args, :process
44
+ lazy_register :process, Lazydoc::Method
45
+
46
+ def initialize(config={})
47
+ initialize_config(config)
48
+ end
49
+
50
+ def call(argv=[])
51
+ process(*argv)
52
+ end
53
+
54
+ def process(*args)
55
+ raise NotImplementedError
56
+ end
57
+ end
58
+
59
+ class CommandError < RuntimeError
60
+ end
61
+ end
@@ -0,0 +1,85 @@
1
+ require 'linecook/command'
2
+
3
+ module Linecook
4
+ class CommandSet < Command
5
+ class << self
6
+ def commands
7
+ # assume/require word names
8
+ @commands ||= {}
9
+ end
10
+
11
+ def parse(argv=ARGV)
12
+ command = super(argv) do |options|
13
+ options.option_break = /\A(?:--\z|[^-])/
14
+ options.preserve_option_break = true
15
+ yield(options) if block_given?
16
+ end
17
+
18
+ # The parser is configured to preserve the break, which is desired
19
+ # when it's a command, but you want to get rid of standard breaks.
20
+ if argv[0] == '--'
21
+ argv.shift
22
+ end
23
+
24
+ command
25
+ end
26
+
27
+ def run(argv=ARGV, &block)
28
+ command = parse(argv) do |options|
29
+ if block_given?
30
+ yield([], self, options)
31
+ end
32
+ end
33
+
34
+ command.call(argv, &block)
35
+ end
36
+
37
+ def command_list
38
+ commands.keys.sort.collect do |name|
39
+ [name, commands[name]]
40
+ end
41
+ end
42
+ end
43
+
44
+ def call(argv=[])
45
+ if argv.empty?
46
+ raise CommandError, "no command specified"
47
+ end
48
+
49
+ command_name = argv.shift
50
+ command_class = self.class.commands[command_name]
51
+
52
+ unless command_class
53
+ raise CommandError, "unknown command: #{command_name.inspect}"
54
+ end
55
+
56
+ # Parse options for the command, but yield with the necessary debugging
57
+ # information - note that command_class is always the latest one to be
58
+ # parsed so start with the command name as the callpath.
59
+ command = command_class.parse(argv) do |options|
60
+ if block_given?
61
+ yield([command_name], command_class, options)
62
+ end
63
+ end
64
+
65
+ if command.kind_of?(CommandSet)
66
+ # The block causes nested CommandSets to roll back out to the main
67
+ # context with the callpath and latest command_class. Literally it
68
+ # wraps the block defined above. Non-CommandSet commands will have
69
+ # nil, not a block passed to call - a NOP.
70
+ command.call(argv) do |callpath, cmdclass, options|
71
+ if block_given?
72
+ callpath.unshift(command_name)
73
+ yield(callpath, cmdclass, options)
74
+ end
75
+ end
76
+ else
77
+ process(command, *argv)
78
+ end
79
+ end
80
+
81
+ def process(command, *args)
82
+ command.call(args)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,20 @@
1
+ require 'configurable'
2
+
3
+ module Linecook
4
+ module CommandUtils
5
+ include Configurable
6
+
7
+ config :quiet, false # -q, --quiet : silence output
8
+
9
+ def sh(cmd)
10
+ $stderr.puts "$ #{cmd}" unless quiet
11
+ system(cmd)
12
+ end
13
+
14
+ def sh!(cmd)
15
+ unless sh(cmd)
16
+ raise CommandError, "non-zero exit status: #{$?.exitstatus}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,72 +1,123 @@
1
- require 'linecook/commands/helper'
2
- require 'linecook/commands/package'
1
+ require 'linecook/commands/compile'
3
2
 
4
3
  module Linecook
5
4
  module Commands
6
-
7
- # :startdoc::desc build a project
5
+ # ::desc build packages
8
6
  #
9
- # Builds some or all packages and helpers in a project, as needed.
7
+ # Builds a list of 'package' recipes into packages. Packages are exported
8
+ # to a directory named like the recipe. Build prints the package dir for
9
+ # each recipe to stdout.
10
10
  #
11
- class Build < Command
12
- config :project_dir, '.', :short => :d # the project directory
13
- config :load_path, [], :short => :I, &c.list # set load paths
14
- config :force, false, :short => :f, &c.flag # force creation
15
- config :quiet, false, &c.flag # silence output
16
-
17
- def glob_helpers(project_dir)
18
- helpers_dir = File.expand_path('helpers', project_dir)
19
- sources = {}
20
- helpers = []
21
-
22
- Dir.glob("#{helpers_dir}/*/**/*").each do |source_file|
23
- next if File.directory?(source_file)
24
- (sources[File.dirname(source_file)] ||= []) << source_file
25
- end
26
-
27
- sources.each_pair do |dir, source_files|
28
- name = dir[(helpers_dir.length + 1)..-1]
29
- helpers << [name, source_files]
11
+ # Recipes are added to the package as the 'run' executable and they are
12
+ # automatically configured with a package file corresponding to the
13
+ # recipe, if it exists.
14
+ #
15
+ # For example:
16
+ #
17
+ # $ echo "write 'echo ' + attrs['msg']" > recipe.rb
18
+ # $ echo "msg: hello world" > recipe.yml
19
+ # $ linecook build recipe.rb
20
+ # /path/to/pwd/recipe
21
+ # $ /path/to/pwd/recipe/run
22
+ # hello world
23
+ #
24
+ # The input directory containing the package files and the output
25
+ # directory for the packages may be specified with options.
26
+ #
27
+ # == Package Specs
28
+ #
29
+ # Package specs can be provided instead of recipes. Specs are
30
+ # comma-separated strings like 'package_file,recipe_file,export_dir' that
31
+ # allow full control over the building of packages. Package files
32
+ #
33
+ # For example:
34
+ #
35
+ # $ echo "write 'echo ' + attrs['msg']" > recipe.rb
36
+ # $ echo "msg: hello world" > input.yml
37
+ # $ linecook build input.yml,recipe.rb,output
38
+ # /path/to/pwd/output
39
+ # $ /path/to/pwd/output/run
40
+ # hello world
41
+ #
42
+ # Providing '-' as an input will cause stdin to be read for additional
43
+ # inputs. In that way a CSV file can serve as a manifest for the packages
44
+ # built by this command.
45
+ #
46
+ class Build < Compile
47
+ class << self
48
+ def set_common_options(options)
49
+ super(options)
50
+ options[:input_dir] = 'packages'
51
+ options[:output_dir] = 'packages'
52
+ options
30
53
  end
31
-
32
- helpers.sort_by {|name, source_files| name }
33
54
  end
34
-
35
- def glob_package_files(package_names)
36
- if package_names.empty?
37
- pattern = File.expand_path('packages/*.yml', project_dir)
38
- Dir.glob(pattern).select {|path| File.file?(path) }
39
- else
40
- package_names.collect do |package_name|
41
- File.expand_path("packages/#{package_name}.yml", project_dir)
55
+ undef_config :package_file
56
+
57
+ def input_dir=(input)
58
+ @package_finder = nil
59
+ super(input)
60
+ end
61
+
62
+ def process(*recipes)
63
+ helper_dirs.each do |helpers_dir|
64
+ compile_helpers(helpers_dir)
65
+ end
66
+
67
+ each_spec(recipes) do |package_file, recipe_file, export_dir|
68
+ export_dir = File.expand_path(export_dir, output_dir)
69
+ if File.exists?(export_dir)
70
+ unless force
71
+ raise CommandError, "already exists: #{export_dir.inspect}"
72
+ end
73
+ FileUtils.rm_r(export_dir)
42
74
  end
75
+
76
+ package_file = File.expand_path(package_file, input_dir)
77
+ recipe_file = File.expand_path(recipe_file)
78
+
79
+ package = Package.new(load_env(package_file))
80
+ cookbook = Cookbook.new(*cookbook_path)
81
+ recipe = Recipe.new(package, cookbook)
82
+ recipe.register_as 'run', :mode => 0744
83
+ recipe.instance_eval File.read(recipe_file), recipe_file
84
+ recipe.register_to package
85
+
86
+ package.export(export_dir)
87
+ puts export_dir
43
88
  end
44
89
  end
45
-
46
- def process(*package_names)
47
- load_path.each do |path|
48
- $:.unshift(path)
90
+
91
+ def each_line(lines)
92
+ lines.each do |line|
93
+ if line == '-'
94
+ while line = gets
95
+ yield line
96
+ end
97
+ else
98
+ yield line
99
+ end
49
100
  end
50
-
51
- helper = Helper.new(
52
- :project_dir => project_dir,
53
- :force => force,
54
- :quiet => true
55
- )
56
-
57
- helpers = glob_helpers(project_dir)
58
- helpers.each do |(name, sources)|
59
- helper.process(name, *sources)
101
+ end
102
+
103
+ def each_spec(lines)
104
+ each_line(lines) do |line|
105
+ yield *parse_spec(line)
60
106
  end
61
-
62
- package = Package.new(
63
- :project_dir => project_dir,
64
- :force => force,
65
- :quiet => quiet
66
- )
67
-
68
- glob_package_files(package_names).collect do |package_file|
69
- package.process(package_file)
107
+ end
108
+
109
+ def parse_spec(spec)
110
+ fields = CSV.parse_line(spec)
111
+
112
+ case fields.length
113
+ when 1 # short form
114
+ recipe_path = fields.at(0)
115
+ base_name = File.basename(recipe_path).chomp('.rb')
116
+ ["#{base_name}.yml", recipe_path, base_name]
117
+ when 3 # long form
118
+ fields
119
+ else
120
+ raise "invalid spec: #{spec.inspect}"
70
121
  end
71
122
  end
72
123
  end
@@ -0,0 +1,181 @@
1
+ require 'fileutils'
2
+ require 'linecook/recipe'
3
+ require 'linecook/commands/compile_helper'
4
+ require 'yaml'
5
+ require 'csv'
6
+
7
+ module Linecook
8
+ module Commands
9
+ # ::desc compile recipes
10
+ #
11
+ # Compiles a list of recipes into a single package and exports the result
12
+ # to the working directory. The recipes are added to the package at their
13
+ # relative path, minus their extname.
14
+ #
15
+ # For example:
16
+ #
17
+ # $ echo "write 'echo hello world'" > recipe.rb
18
+ # $ linecook compile recipe.rb
19
+ # $ sh recipe
20
+ # hello world
21
+ #
22
+ # Providing '-' as a recipe will cause stdin to be compiled as a recipe to
23
+ # stdout.
24
+ class Compile < Command
25
+ class << self
26
+ def parse(argv=ARGV)
27
+ super(argv) do |options|
28
+ options.on('-I DIRECTORY', 'prepend to LOAD_PATH') do |path|
29
+ $LOAD_PATH.unshift File.expand_path(path)
30
+ end
31
+
32
+ options.on('-r LIBRARY', 'require the library') do |path|
33
+ require(path)
34
+ end
35
+
36
+ options.on('-G GEMNAME', 'add gem to cookbook path', :option_type => :list) do |name|
37
+ specs = Gem.source_index.find_name(name)
38
+ if specs.empty?
39
+ raise CommandError, "could not find gem: #{name.inspect}"
40
+ end
41
+ (options[:cookbook_path] ||= []) << specs.first.full_gem_path
42
+ end
43
+
44
+ options.on('-g', '--gems', 'add latest cookbook gems') do |name|
45
+ (options[:cookbook_path] ||= []).concat cookbook_gem_paths
46
+ end
47
+
48
+ options.on('-c', '--common', 'use common flags') do
49
+ set_common_options(options)
50
+ end
51
+
52
+ if block_given?
53
+ yield(options)
54
+ end
55
+ end
56
+ end
57
+
58
+ def set_common_options(options)
59
+ cookbook_path = (options[:cookbook_path] ||= [])
60
+ cookbook_path << '.'
61
+ cookbook_path.concat cookbook_gem_paths
62
+ (options[:helper_dirs] ||= []) << 'helpers'
63
+ options
64
+ end
65
+
66
+ def cookbook_gem_paths(latest=true)
67
+ Cookbook.gemspecs(latest).collect do |gemspec|
68
+ gemspec.full_gem_path
69
+ end
70
+ end
71
+
72
+ def gemspecs(latest=true)
73
+ return [] unless Object.const_defined?(:Gem)
74
+
75
+ index = Gem.source_index
76
+ specs = latest ? index.latest_specs : index.gems.values
77
+
78
+ specs.select do |spec|
79
+ cookbook_file = File.expand_path(default_file_name, spec.full_gem_path)
80
+ File.exists?(cookbook_file)
81
+ end
82
+ end
83
+ end
84
+
85
+ config :cookbook_path, [], :delimiter => ':' # -C PATH : cookbook path
86
+ config :helper_dirs, [] # -L DIRECTORY : compile helpers
87
+ config :package_file, nil # -p PATH : package config file
88
+ config :input_dir, '.', :writer => :input_dir= # -i DIRECTORY : the input dir
89
+ config :output_dir, '.', :writer => :output_dir= # -o DIRECTORY : the output dir
90
+ config :force, false # -f, --force : overwrite existing
91
+
92
+ def input_dir=(input)
93
+ @input_dir = File.expand_path(input)
94
+ end
95
+
96
+ def output_dir=(input)
97
+ @output_dir = File.expand_path(input)
98
+ end
99
+
100
+ def process(*recipes)
101
+ helper_dirs.each do |helper_dir|
102
+ compile_helpers(helper_dir)
103
+ end
104
+
105
+ package = Package.new(load_env(package_file))
106
+ cookbook = Cookbook.new(*cookbook_path)
107
+ stdout = StringIO.new
108
+
109
+ recipes.each do |recipe_path|
110
+ recipe = Recipe.new(package, cookbook)
111
+
112
+ if recipe_path == '-'
113
+ recipe.instance_eval $stdin.read, 'stdin'
114
+ stdout.print recipe
115
+ else
116
+ recipe_path = File.expand_path(recipe_path)
117
+ recipe.register_as relative_path(input_dir, recipe_path).chomp('.rb')
118
+ recipe.instance_eval File.read(recipe_path), recipe_path
119
+ recipe.register_to package
120
+ end
121
+ end
122
+
123
+ package.export(output_dir) do |src, dest|
124
+ unless force
125
+ raise CommandError, "already exists: #{dest.inspect}"
126
+ end
127
+
128
+ FileUtils.rm_rf(dest)
129
+ true
130
+ end
131
+
132
+ # print to $stdout after export to ensure files will be available
133
+ $stdout << stdout.string
134
+ end
135
+
136
+ def relative_path(dir, path)
137
+ if path.index(dir) == 0 && path != dir
138
+ path[dir.length + 1, path.length - dir.length]
139
+ else
140
+ File.basename(path)
141
+ end
142
+ end
143
+
144
+ def load_env(package_file)
145
+ env = package_file && File.exists?(package_file) ? YAML.load_file(package_file) : nil
146
+ env.nil? ? {} : env
147
+ end
148
+
149
+ def glob_helpers(helper_dir)
150
+ sources = {}
151
+ helpers = []
152
+
153
+ Dir.glob("#{helper_dir}/*/**/*").each do |source_file|
154
+ next if File.directory?(source_file)
155
+ (sources[File.dirname(source_file)] ||= []) << source_file
156
+ end
157
+
158
+ sources.each_pair do |dir, source_files|
159
+ name = dir[(helper_dir.length + 1)..-1]
160
+ helpers << [name, source_files]
161
+ end
162
+
163
+ helpers.sort_by {|name, source_files| name }
164
+ end
165
+
166
+ def compile_helpers(helper_dir)
167
+ compiler = CompileHelper.new(
168
+ :force => force,
169
+ :quiet => true
170
+ )
171
+
172
+ helpers = glob_helpers(helper_dir)
173
+ helpers.each do |(name, sources)|
174
+ compiler.process(name, *sources)
175
+ end
176
+
177
+ $LOAD_PATH.unshift compiler.output_dir
178
+ end
179
+ end
180
+ end
181
+ end