linecook 0.6.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History +139 -0
  2. data/HowTo/Control Virtual Machines +106 -0
  3. data/HowTo/Generate Scripts +263 -0
  4. data/HowTo/Run Scripts +87 -0
  5. data/HowTo/Setup Virtual Machines +76 -0
  6. data/License.txt +1 -1
  7. data/README +78 -59
  8. data/bin/linecook +12 -5
  9. data/bin/linecook_run +45 -0
  10. data/bin/linecook_scp +50 -0
  11. data/lib/linecook.rb +1 -3
  12. data/lib/linecook/attributes.rb +49 -12
  13. data/lib/linecook/commands.rb +9 -4
  14. data/lib/linecook/commands/build.rb +69 -0
  15. data/lib/linecook/commands/command.rb +13 -3
  16. data/lib/linecook/commands/command_error.rb +6 -0
  17. data/lib/linecook/commands/env.rb +74 -8
  18. data/lib/linecook/commands/helper.rb +271 -24
  19. data/lib/linecook/commands/init.rb +10 -6
  20. data/lib/linecook/commands/package.rb +36 -18
  21. data/lib/linecook/commands/run.rb +66 -0
  22. data/lib/linecook/commands/snapshot.rb +114 -0
  23. data/lib/linecook/commands/ssh.rb +39 -0
  24. data/lib/linecook/commands/start.rb +34 -0
  25. data/lib/linecook/commands/state.rb +32 -0
  26. data/lib/linecook/commands/stop.rb +22 -0
  27. data/lib/linecook/commands/vbox_command.rb +130 -0
  28. data/lib/linecook/cookbook.rb +112 -55
  29. data/lib/linecook/package.rb +293 -109
  30. data/lib/linecook/proxy.rb +19 -0
  31. data/lib/linecook/recipe.rb +321 -62
  32. data/lib/linecook/template.rb +7 -101
  33. data/lib/linecook/test.rb +196 -141
  34. data/lib/linecook/test/command_parser.rb +75 -0
  35. data/lib/linecook/test/file_test.rb +153 -35
  36. data/lib/linecook/test/shell_test.rb +176 -0
  37. data/lib/linecook/utils.rb +25 -7
  38. data/lib/linecook/version.rb +4 -4
  39. data/templates/Rakefile +44 -47
  40. data/templates/_gitignore +1 -1
  41. data/templates/attributes/project_name.rb +4 -4
  42. data/templates/config/ssh +15 -0
  43. data/templates/files/help.txt +1 -0
  44. data/templates/helpers/project_name/assert_content_equal.erb +15 -0
  45. data/templates/helpers/project_name/create_dir.erb +9 -0
  46. data/templates/helpers/project_name/create_file.erb +8 -0
  47. data/templates/helpers/project_name/install_file.erb +8 -0
  48. data/templates/packages/abox.yml +4 -0
  49. data/templates/recipes/abox.rb +22 -0
  50. data/templates/recipes/abox_test.rb +14 -0
  51. data/templates/templates/todo.txt.erb +3 -0
  52. data/templates/test/project_name_test.rb +19 -0
  53. data/templates/test/test_helper.rb +14 -0
  54. metadata +43 -41
  55. data/cookbook +0 -0
  56. data/lib/linecook/commands/helpers.rb +0 -28
  57. data/lib/linecook/commands/vbox.rb +0 -85
  58. data/lib/linecook/helper.rb +0 -117
  59. data/lib/linecook/shell.rb +0 -11
  60. data/lib/linecook/shell/posix.rb +0 -145
  61. data/lib/linecook/shell/test.rb +0 -254
  62. data/lib/linecook/shell/unix.rb +0 -117
  63. data/lib/linecook/shell/utils.rb +0 -138
  64. data/templates/README +0 -90
  65. data/templates/files/file.txt +0 -1
  66. data/templates/helpers/project_name/echo.erb +0 -5
  67. data/templates/recipes/project_name.rb +0 -20
  68. data/templates/scripts/project_name.yml +0 -7
  69. data/templates/templates/template.txt.erb +0 -3
  70. data/templates/vbox/setup/virtual_box +0 -86
  71. data/templates/vbox/ssh/id_rsa +0 -27
  72. data/templates/vbox/ssh/id_rsa.pub +0 -1
@@ -0,0 +1,176 @@
1
+ require 'linecook/test/regexp_escape'
2
+ require 'linecook/test/command_parser'
3
+
4
+ module Linecook
5
+ module Test
6
+ module ShellTest
7
+
8
+ def setup
9
+ super
10
+ @notify_method_name = true
11
+ end
12
+
13
+ # Returns true if the ENV variable 'VERBOSE' is true. When verbose,
14
+ # ShellTest prints the expanded commands of sh_test to $stdout.
15
+ def verbose?
16
+ verbose = ENV['VERBOSE']
17
+ verbose && verbose =~ /^true$/i ? true : false
18
+ end
19
+
20
+ # Sets the specified ENV variables and returns the *current* env.
21
+ # If replace is true, current ENV variables are replaced; otherwise
22
+ # the new env variables are simply added to the existing set.
23
+ def set_env(env={}, replace=false)
24
+ current_env = {}
25
+ ENV.each_pair do |key, value|
26
+ current_env[key] = value
27
+ end
28
+
29
+ ENV.clear if replace
30
+
31
+ env.each_pair do |key, value|
32
+ if value.nil?
33
+ ENV.delete(key)
34
+ else
35
+ ENV[key] = value
36
+ end
37
+ end if env
38
+
39
+ current_env
40
+ end
41
+
42
+ # Sets the specified ENV variables for the duration of the block.
43
+ # If replace is true, current ENV variables are replaced; otherwise
44
+ # the new env variables are simply added to the existing set.
45
+ #
46
+ # Returns the block return.
47
+ def with_env(env={}, replace=false)
48
+ current_env = nil
49
+ begin
50
+ current_env = set_env(env, replace)
51
+ yield
52
+ ensure
53
+ if current_env
54
+ set_env(current_env, true)
55
+ end
56
+ end
57
+ end
58
+
59
+ def sh(cmd)
60
+ if @notify_method_name && verbose?
61
+ @notify_method_name = false
62
+ puts
63
+ puts method_name
64
+ end
65
+
66
+ start = Time.now
67
+ result = `#{cmd}`
68
+ finish = Time.now
69
+
70
+ if verbose?
71
+ elapsed = "%.3f" % [finish-start]
72
+ puts " (#{elapsed}s) #{cmd}"
73
+ end
74
+
75
+ result
76
+ end
77
+
78
+ def assert_script(script, options={})
79
+ _assert_script outdent(script), options
80
+ end
81
+
82
+ def _assert_script(script, options={})
83
+ commands = CommandParser.new(options).parse(script)
84
+ commands.each do |cmd, output, status|
85
+ result = sh(cmd)
86
+
87
+ _assert_output_equal(output, result, cmd) if output
88
+ assert_equal(status, $?.exitstatus, cmd) if status
89
+ end
90
+ end
91
+
92
+ def assert_script_match(script, options={})
93
+ _assert_script_match outdent(script), options
94
+ end
95
+
96
+ def _assert_script_match(script, options={})
97
+ commands = CommandParser.new(options).parse(script)
98
+ commands.each do |cmd, output, status|
99
+ result = sh(cmd)
100
+
101
+ _assert_alike(output, result, cmd) if output
102
+ assert_equal(status, $?.exitstatus, cmd) if status
103
+ end
104
+ end
105
+
106
+ # Asserts whether or not the a and b strings are equal, with a more
107
+ # readable output than assert_equal for large strings (especially large
108
+ # strings with significant whitespace).
109
+ def assert_output_equal(a, b, msg=nil)
110
+ _assert_output_equal outdent(a), b, msg
111
+ end
112
+
113
+ def _assert_output_equal(a, b, msg=nil)
114
+ if a == b
115
+ assert true
116
+ else
117
+ flunk %Q{
118
+ #{msg}
119
+ ==================== expected output ====================
120
+ #{whitespace_escape(a)}
121
+ ======================== but was ========================
122
+ #{whitespace_escape(b)}
123
+ =========================================================
124
+ }
125
+ end
126
+ end
127
+
128
+ # Asserts whether or not b is like a (which should be a Regexp), and
129
+ # provides a more readable output in the case of a failure as compared
130
+ # with assert_match.
131
+ #
132
+ # If a is a string it is turned into a RegexpEscape.
133
+ def assert_alike(a, b, msg=nil)
134
+ a = outdent(a) if a.kind_of?(String)
135
+ _assert_alike a, b, msg
136
+ end
137
+
138
+ def _assert_alike(a, b, msg=nil)
139
+ if a.kind_of?(String)
140
+ a = RegexpEscape.new(a)
141
+ end
142
+
143
+ if b =~ a
144
+ assert true
145
+ else
146
+ flunk %Q{
147
+ #{msg}
148
+ ================= expected output like ==================
149
+ #{whitespace_escape(a)}
150
+ ======================== but was ========================
151
+ #{whitespace_escape(b)}
152
+ =========================================================
153
+ }
154
+ end
155
+ end
156
+
157
+ # helper for stripping indentation off a string
158
+ def outdent(str)
159
+ str =~ /\A(?:\s*?\n)( *)(.*)\z/m ? $2.gsub!(/^ {0,#{$1.length}}/, '') : str
160
+ end
161
+
162
+ # helper for formatting escaping whitespace into readable text
163
+ def whitespace_escape(str)
164
+ str.to_s.gsub(/\s/) do |match|
165
+ case match
166
+ when "\n" then "\\n\n"
167
+ when "\t" then "\\t"
168
+ when "\r" then "\\r"
169
+ when "\f" then "\\f"
170
+ else match
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -1,17 +1,35 @@
1
+ require 'yaml'
2
+
1
3
  module Linecook
2
4
  module Utils
3
5
  module_function
4
6
 
5
- def nest_hash
6
- Hash.new {|hash, key| hash[key] = nest_hash }
7
+ def load_config(path)
8
+ (path ? YAML.load_file(path) : nil) || {}
7
9
  end
8
10
 
9
- def serial_merge(*hashes)
10
- attrs = {}
11
- while overrides = hashes.shift
12
- attrs = deep_merge(attrs, overrides)
11
+ def arrayify(obj)
12
+ case obj
13
+ when nil then []
14
+ when String then obj.split(':')
15
+ else obj
16
+ end
17
+ end
18
+
19
+ def hashify(obj)
20
+ case obj
21
+ when Hash then obj
22
+ when nil then {}
23
+ when Array
24
+ hash = {}
25
+ obj.each {|entry| hash[entry] = entry }
26
+ hash
27
+
28
+ when String
29
+ hashify obj.split(':')
30
+
31
+ else nil
13
32
  end
14
- attrs
15
33
  end
16
34
 
17
35
  def deep_merge(a, b)
@@ -1,8 +1,8 @@
1
1
  module Linecook
2
- MAJOR = 0
3
- MINOR = 6
4
- TINY = 2
5
-
2
+ MAJOR = 1
3
+ MINOR = 0
4
+ TINY = 0
5
+
6
6
  VERSION = "#{MAJOR}.#{MINOR}.#{TINY}"
7
7
  WEBSITE = "http://github.com/pinnacol/linecook"
8
8
  end
data/templates/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
1
  require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
2
4
 
3
5
  #
4
6
  # Gem tasks
5
7
  #
6
8
 
7
- require 'rake/rdoctask'
8
- require 'rake/gempackagetask'
9
-
10
9
  def gemspec
11
- @gemspec ||= eval(File.read('<%= project_name %>.gemspec'), TOPLEVEL_BINDING)
10
+ @gemspec ||= begin
11
+ gemspec_path = File.expand_path('../<%= project_name %>.gemspec', __FILE__)
12
+ eval(File.read(gemspec_path), TOPLEVEL_BINDING)
13
+ end
12
14
  end
13
15
 
14
16
  Rake::GemPackageTask.new(gemspec) do |pkg|
@@ -72,56 +74,40 @@ end
72
74
  # Linecook Helpers
73
75
  #
74
76
 
75
- lib_dir = File.expand_path("../lib", __FILE__)
76
- helpers_dir = File.expand_path("../helpers", __FILE__)
77
+ package = ENV['PACKAGE']
78
+ force = (ENV['FORCE'] == 'true')
77
79
 
78
- sources = {}
79
- helpers = []
80
-
81
- Dir.glob("#{helpers_dir}/**/*").each do |source|
82
- next if File.directory?(source)
83
- (sources[File.dirname(source)] ||= []) << source
80
+ desc "build helpers and packages"
81
+ task :build => :bundle do
82
+ sh "bundle exec linecook build #{force ? '--force ' : nil}#{package}"
84
83
  end
85
84
 
86
- sources.each_pair do |dir, sources|
87
- name = dir[(helpers_dir.length + 1)..-1]
88
- target = File.join(lib_dir, 'linebook', "#{name}.rb")
89
-
90
- file target => sources + [dir] do
91
- system "bundle exec linecook helper '#{name}' --force"
92
- end
93
-
94
- helpers << target
85
+ desc "run packages"
86
+ task :run => :build do
87
+ sh "bundle exec linecook run #{package}"
95
88
  end
96
89
 
97
- desc "generate helpers"
98
- task :helpers => helpers + [:bundle]
99
-
100
- #
101
- # Linecook Scripts
102
- #
90
+ desc "start each vm at CURRENT"
91
+ task :start => :bundle do
92
+ sh 'bundle exec linecook start --socket --snapshot CURRENT'
93
+ end
103
94
 
104
- scripts = Dir.glob("scripts/*.yml")
105
- dependencies = Dir.glob('{attributes,files,recipes,templates}/**/*')
95
+ desc "snapshot each vm to a new CURRENT"
96
+ task :snapshot => :bundle do
97
+ sh 'bundle exec linecook snapshot CURRENT'
98
+ end
106
99
 
107
- scripts.each do |source|
108
- target = source.chomp('.yml')
109
- name = File.basename(target)
110
-
111
- namespace :scripts do
112
- file target => dependencies + [source] + helpers do
113
- sh "bundle exec linecook package '#{source}' '#{target}' --force"
114
- end
115
-
116
- desc "generate the script: #{name}"
117
- task name => target
118
- end
119
-
120
- task :scripts => target
100
+ desc "reset each vm to BASE"
101
+ task :reset_base => :bundle do
102
+ sh 'bundle exec linecook snapshot --reset BASE'
103
+ sh 'bundle exec linecook snapshot CURRENT'
104
+ sh 'bundle exec linecook start --socket --snapshot CURRENT'
121
105
  end
122
106
 
123
- desc "generate scripts"
124
- task :scripts
107
+ desc "stop each vm"
108
+ task :stop => :bundle do
109
+ sh 'bundle exec linecook stop'
110
+ end
125
111
 
126
112
  #
127
113
  # Test tasks
@@ -130,9 +116,10 @@ task :scripts
130
116
  desc 'Default: Run tests.'
131
117
  task :default => :test
132
118
 
133
- desc 'Run the tests'
134
- task :test => :helpers do
119
+ desc 'Run the tests assuming each vm is setup'
120
+ task :quicktest => :build do
135
121
  tests = Dir.glob('test/**/*_test.rb')
122
+ tests.delete_if {|test| test =~ /_test\/test_/ }
136
123
 
137
124
  if ENV['RCOV'] == 'true'
138
125
  FileUtils.rm_rf File.expand_path('../coverage', __FILE__)
@@ -142,6 +129,16 @@ task :test => :helpers do
142
129
  end
143
130
  end
144
131
 
132
+ desc 'Run the tests'
133
+ task :test do
134
+ begin
135
+ Rake::Task["start"].invoke
136
+ Rake::Task["quicktest"].invoke
137
+ ensure
138
+ Rake::Task["stop"].execute(nil)
139
+ end
140
+ end
141
+
145
142
  desc 'Run rcov'
146
143
  task :rcov do
147
144
  ENV['RCOV'] = 'true'
data/templates/_gitignore CHANGED
@@ -2,4 +2,4 @@
2
2
  .bundle
3
3
  *.gem
4
4
  /rdoc
5
- vbox/log
5
+ vm
@@ -1,4 +1,4 @@
1
- # Define default recipe attributes here and include in a recipe using
2
- # 'attributes "filename"'. Script attributes override these values.
3
- attrs['<%= project_name %>']['letters'] = ['a', 'b', 'c']
4
- attrs['<%= project_name %>']['numbers'] = [1, 2, 3]
1
+ # Define default recipe attributes here.
2
+ # (note string keys are usually better than symbols)
3
+ attrs['<%= project_name %>']['year'] = Time.now.year
4
+ attrs['<%= project_name %>']['resolutions'] = []
@@ -0,0 +1,15 @@
1
+ # Declare hosts for packages - the vm name can be specified in a special
2
+ # comment following the Host declaration (defaults to the host).
3
+ Host abox # [abox]
4
+ Port 2220
5
+
6
+ # Define global configs here. By default tests run vs this default host.
7
+ Host *
8
+ HostName localhost
9
+ User linecook
10
+ Port 2220
11
+ UserKnownHostsFile /dev/null
12
+ StrictHostKeyChecking no
13
+ IdentitiesOnly yes
14
+ ControlMaster auto
15
+ ControlPath /tmp/socket-%r@%h:%p
@@ -0,0 +1 @@
1
+ * https://github.com/pinnacol/linecook
@@ -0,0 +1,15 @@
1
+ Asserts the content of the file is as expected. Prints a diff and exits 1 if
2
+ the content is not as expected.
3
+ (expected, file)
4
+ --
5
+ expected='<%= '<' %>%= expected.rstrip %<%= '>' %>'
6
+ actual=$(cat <%= '<' %>%= file %<%= '>' %>)
7
+
8
+ echo "Check file: <%= '<' %>%= file %<%= '>' %>"
9
+ if [ "$actual" != "$expected" ]
10
+ then
11
+ echo "[$0] unequal output: <%= '<' %>%= file %<%= '>' %>" >&2
12
+ diff <(cat <<< "$expected") <(cat <<< "$actual") >&2
13
+ exit 1
14
+ fi
15
+
@@ -0,0 +1,9 @@
1
+ Creates a directory and parents as needed.
2
+ (dir)
3
+ --
4
+ if ! [ -d <%= '<' %>%= dir %<%= '>' %> ]
5
+ then
6
+ echo "Create dir: <%= '<' %>%= dir %<%= '>' %>"
7
+ mkdir -p <%= '<' %>%= dir %<%= '>' %>
8
+ fi
9
+
@@ -0,0 +1,8 @@
1
+ Creates a file with the specified content.
2
+ (file, content)
3
+ --
4
+ <%= '<' %>% create_dir File.dirname(file) %<%= '>' %>
5
+
6
+ echo "Create file: <%= '<' %>%= file %<%= '>' %>"
7
+ echo -n '<%= '<' %>%= content %<%= '>' %>' > <%= '<' %>%= file %<%= '>' %>
8
+
@@ -0,0 +1,8 @@
1
+ Installs a file.
2
+ (source, target)
3
+ --
4
+ <%= '<' %>% create_dir File.dirname(target) %<%= '>' %>
5
+
6
+ echo "Install file: <%= '<' %>%= target %<%= '>' %>"
7
+ cp <%= '<' %>%= source %<%= '>' %> <%= '<' %>%= target %<%= '>' %>
8
+