linecook 0.6.2 → 1.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 (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
+