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