linecook 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{History → History.rdoc} +3 -2
- data/README.rdoc +93 -0
- data/bin/linecook +32 -56
- data/bin/linecook_run +19 -6
- data/bin/linecook_scp +12 -4
- data/doc/vm_setup.rdoc +75 -0
- data/lib/linecook.rb +3 -2
- data/lib/linecook/attributes.rb +33 -8
- data/lib/linecook/command.rb +61 -0
- data/lib/linecook/command_set.rb +85 -0
- data/lib/linecook/command_utils.rb +20 -0
- data/lib/linecook/commands/build.rb +108 -57
- data/lib/linecook/commands/compile.rb +181 -0
- data/lib/linecook/commands/{helper.rb → compile_helper.rb} +123 -94
- data/lib/linecook/commands/run.rb +43 -39
- data/lib/linecook/commands/snapshot.rb +24 -24
- data/lib/linecook/commands/ssh.rb +7 -7
- data/lib/linecook/commands/start.rb +10 -10
- data/lib/linecook/commands/state.rb +7 -7
- data/lib/linecook/commands/stop.rb +3 -3
- data/lib/linecook/commands/{vbox_command.rb → virtual_box_command.rb} +31 -29
- data/lib/linecook/cookbook.rb +149 -131
- data/lib/linecook/executable.rb +28 -0
- data/lib/linecook/package.rb +177 -361
- data/lib/linecook/proxy.rb +4 -10
- data/lib/linecook/recipe.rb +289 -369
- data/lib/linecook/test.rb +114 -98
- data/lib/linecook/utils.rb +31 -41
- data/lib/linecook/version.rb +2 -6
- metadata +120 -68
- data/HowTo/Control Virtual Machines +0 -106
- data/HowTo/Generate Scripts +0 -268
- data/HowTo/Run Scripts +0 -87
- data/HowTo/Setup Virtual Machines +0 -76
- data/README +0 -117
- data/lib/linecook/commands.rb +0 -11
- data/lib/linecook/commands/command.rb +0 -58
- data/lib/linecook/commands/command_error.rb +0 -12
- data/lib/linecook/commands/env.rb +0 -89
- data/lib/linecook/commands/init.rb +0 -86
- data/lib/linecook/commands/package.rb +0 -57
- data/lib/linecook/template.rb +0 -17
- data/lib/linecook/test/command_parser.rb +0 -75
- data/lib/linecook/test/file_test.rb +0 -197
- data/lib/linecook/test/regexp_escape.rb +0 -86
- data/lib/linecook/test/shell_test.rb +0 -177
- data/lib/linecook/test/shim.rb +0 -71
- data/templates/Gemfile +0 -3
- data/templates/Rakefile +0 -146
- data/templates/_gitignore +0 -4
- data/templates/attributes/project_name.rb +0 -3
- data/templates/config/ssh +0 -14
- data/templates/cookbook +0 -10
- data/templates/files/example.txt +0 -1
- data/templates/helpers/project_name/echo.erb +0 -4
- data/templates/packages/abox.yml +0 -2
- data/templates/project_name.gemspec +0 -30
- data/templates/recipes/abox.rb +0 -16
- data/templates/templates/example.erb +0 -1
- data/templates/test/project_name_test.rb +0 -24
- 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/
|
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
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|