loom-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +99 -0
  6. data/Guardfile +54 -0
  7. data/Rakefile +6 -0
  8. data/bin/loom +185 -0
  9. data/lib/env/development.rb +1 -0
  10. data/lib/loom.rb +44 -0
  11. data/lib/loom/all.rb +20 -0
  12. data/lib/loom/config.rb +106 -0
  13. data/lib/loom/core_ext.rb +37 -0
  14. data/lib/loom/dsl.rb +60 -0
  15. data/lib/loom/facts.rb +13 -0
  16. data/lib/loom/facts/all.rb +2 -0
  17. data/lib/loom/facts/fact_file_provider.rb +86 -0
  18. data/lib/loom/facts/fact_set.rb +138 -0
  19. data/lib/loom/host_spec.rb +32 -0
  20. data/lib/loom/inventory.rb +124 -0
  21. data/lib/loom/logger.rb +141 -0
  22. data/lib/loom/method_signature.rb +174 -0
  23. data/lib/loom/mods.rb +4 -0
  24. data/lib/loom/mods/action_proxy.rb +105 -0
  25. data/lib/loom/mods/all.rb +3 -0
  26. data/lib/loom/mods/mod_loader.rb +80 -0
  27. data/lib/loom/mods/module.rb +113 -0
  28. data/lib/loom/pattern.rb +15 -0
  29. data/lib/loom/pattern/all.rb +7 -0
  30. data/lib/loom/pattern/definition_context.rb +74 -0
  31. data/lib/loom/pattern/dsl.rb +176 -0
  32. data/lib/loom/pattern/hook.rb +28 -0
  33. data/lib/loom/pattern/loader.rb +48 -0
  34. data/lib/loom/pattern/reference.rb +71 -0
  35. data/lib/loom/pattern/reference_set.rb +169 -0
  36. data/lib/loom/pattern/result_reporter.rb +77 -0
  37. data/lib/loom/runner.rb +209 -0
  38. data/lib/loom/shell.rb +12 -0
  39. data/lib/loom/shell/all.rb +10 -0
  40. data/lib/loom/shell/api.rb +48 -0
  41. data/lib/loom/shell/cmd_result.rb +33 -0
  42. data/lib/loom/shell/cmd_wrapper.rb +164 -0
  43. data/lib/loom/shell/core.rb +226 -0
  44. data/lib/loom/shell/harness_blob.rb +26 -0
  45. data/lib/loom/shell/harness_command_builder.rb +50 -0
  46. data/lib/loom/shell/session.rb +25 -0
  47. data/lib/loom/trap.rb +44 -0
  48. data/lib/loom/version.rb +3 -0
  49. data/lib/loomext/all.rb +4 -0
  50. data/lib/loomext/corefacts.rb +6 -0
  51. data/lib/loomext/corefacts/all.rb +8 -0
  52. data/lib/loomext/corefacts/facter_provider.rb +24 -0
  53. data/lib/loomext/coremods.rb +5 -0
  54. data/lib/loomext/coremods/all.rb +13 -0
  55. data/lib/loomext/coremods/exec.rb +50 -0
  56. data/lib/loomext/coremods/files.rb +104 -0
  57. data/lib/loomext/coremods/net.rb +33 -0
  58. data/lib/loomext/coremods/package/adapter.rb +100 -0
  59. data/lib/loomext/coremods/package/package.rb +62 -0
  60. data/lib/loomext/coremods/user.rb +82 -0
  61. data/lib/loomext/coremods/vm.rb +0 -0
  62. data/lib/loomext/coremods/vm/all.rb +6 -0
  63. data/lib/loomext/coremods/vm/vbox.rb +84 -0
  64. data/loom.gemspec +39 -0
  65. data/loom/inventory.yml +13 -0
  66. data/scripts/harness.sh +242 -0
  67. data/spec/loom/host_spec_spec.rb +101 -0
  68. data/spec/loom/inventory_spec.rb +154 -0
  69. data/spec/loom/method_signature_spec.rb +275 -0
  70. data/spec/loom/pattern/dsl_spec.rb +207 -0
  71. data/spec/loom/shell/cmd_wrapper_spec.rb +239 -0
  72. data/spec/loom/shell/harness_blob_spec.rb +42 -0
  73. data/spec/loom/shell/harness_command_builder_spec.rb +36 -0
  74. data/spec/runloom.sh +35 -0
  75. data/spec/scripts/harness_spec.rb +385 -0
  76. data/spec/spec_helper.rb +94 -0
  77. data/spec/test.loom +370 -0
  78. data/spec/test_loom_spec.rb +57 -0
  79. metadata +287 -0
@@ -0,0 +1,207 @@
1
+ require "loom/facts"
2
+ require "loom/pattern"
3
+ require "loom/shell"
4
+
5
+ describe Loom::Pattern::DSL do
6
+
7
+ # Fake API for tests. This object should raise an error on any command
8
+ # execution.
9
+ let(:fake_shell) { Loom::Shell::FakeApi.new }
10
+ let(:a_fact_set) do
11
+ Loom::Facts::FactSet.new('fake.host', :fact_one => 1, :fact_two => :two)
12
+ end
13
+
14
+ before do
15
+ # bit buckets the logs
16
+ @logger_io = StringIO.new
17
+ Loom.configure do |config|
18
+ config.log_device = @logger_io
19
+ end
20
+
21
+ @reference_set = Loom::Pattern::ReferenceSet::Builder
22
+ .create(loom_file, ':loom_file')
23
+ end
24
+
25
+ let(:pattern_under_test) { @reference_set['pattern_under_test'] }
26
+ let(:inner_pattern) { @reference_set['inner:an_inner_pattern'] }
27
+
28
+ context "pattern basics" do
29
+
30
+ let(:loom_file) do
31
+ <<EOS
32
+ desc "a description of pattern_under_test"
33
+ pattern :pattern_under_test do |loom, facts|
34
+ loom.do_outer_thing
35
+ end
36
+
37
+ module Inner
38
+ include Loom::Pattern
39
+
40
+ desc "an inner pattern"
41
+ pattern :an_inner_pattern do |loom, facts|
42
+ loom.do_inner_thing
43
+ end
44
+ end
45
+ EOS
46
+ end
47
+
48
+ it "defines a Pattern::Reference" do
49
+ expect(pattern_under_test).to be_a Loom::Pattern::Reference
50
+ end
51
+
52
+ it "defines a reference with a desc" do
53
+ expect(pattern_under_test.desc).to(
54
+ eql "a description of pattern_under_test")
55
+ end
56
+
57
+ it "defines an inner reference with a desc" do
58
+ expect(inner_pattern.desc).to eql "an inner pattern"
59
+ end
60
+
61
+ it "is runnable" do
62
+ pattern_under_test.call fake_shell, a_fact_set
63
+ expect(fake_shell.cmd_executions.first).to be :do_outer_thing
64
+ end
65
+ end
66
+
67
+ context "#let" do
68
+
69
+ let(:loom_file) do
70
+ <<EOS
71
+ let(:let_var_1) { "let var 1"}
72
+ let(:let_var_2) { "let var 2"}
73
+
74
+ desc "a description of pattern_under_test"
75
+ pattern :pattern_under_test do |loom, facts|
76
+ loom.do_outer_thing(let_var_1, let_var_2)
77
+ end
78
+
79
+ desc "a pattern that will raise an erorr"
80
+ pattern :bogus_pattern do |loom, facts|
81
+ loom.do_outer_thing(let_var_3)
82
+ end
83
+
84
+ module Inner
85
+ include Loom::Pattern
86
+
87
+ let(:let_var_2) { "override let var 2"}
88
+ let(:let_var_3) { "let var 3"}
89
+
90
+ desc "an inner pattern"
91
+ pattern :an_inner_pattern do |loom, facts|
92
+ loom.do_inner_thing(let_var_1, let_var_2, let_var_3)
93
+ end
94
+ end
95
+ EOS
96
+ end
97
+
98
+ it "defines :let declartions at the top level" do
99
+ pattern_under_test.call fake_shell, a_fact_set
100
+ expect(fake_shell.cmd_execution_args.first).to(
101
+ eql ["let var 1", "let var 2"])
102
+ end
103
+
104
+ it "defines :let declartions at inner scopes, innacessible to outer" do
105
+ expect do
106
+ @reference_set['bogus_pattern'].call fake_shell, a_fact_set
107
+ end.to raise_error /^undefined local variable/
108
+ end
109
+
110
+ it "defines :let declarations at inner scopes, overriding outer scope" do
111
+ inner_pattern.call fake_shell, a_fact_set
112
+ expect(fake_shell.cmd_execution_args.first).to(
113
+ eql ["let var 1", "override let var 2", "let var 3"])
114
+ end
115
+ end
116
+
117
+ context "#with_facts" do
118
+
119
+ let(:loom_file) do
120
+ <<EOS
121
+ with_facts :outer_fact => :outer
122
+
123
+ desc "a description of pattern_under_test"
124
+ pattern :pattern_under_test do |loom, facts|
125
+ loom.do_outer_thing facts[:outer_fact]
126
+ end
127
+
128
+ module Inner
129
+ include Loom::Pattern
130
+
131
+ with_facts :inner_fact => :inner
132
+
133
+ desc "an inner pattern"
134
+ pattern :an_inner_pattern do |loom, facts|
135
+ loom.do_inner_thing(facts[:outer_fact], facts[:inner_fact])
136
+ end
137
+ end
138
+ EOS
139
+ end
140
+
141
+ it "defines fact sets at the top level" do
142
+ pattern_under_test.call fake_shell, a_fact_set
143
+ expect(fake_shell.cmd_execution_args.first).to(
144
+ eql [:outer])
145
+ end
146
+
147
+ it "provides fact sets that get merged into inner modules" do
148
+ inner_pattern.call fake_shell, a_fact_set
149
+ expect(fake_shell.cmd_execution_args.first).to(
150
+ eql [:outer, :inner])
151
+ end
152
+ end
153
+
154
+ context "hooks" do
155
+
156
+ let(:loom_file) do
157
+ <<EOS
158
+
159
+ let(:hook_order) { [] }
160
+
161
+ before do hook_order.push("outer before hook") end
162
+ after do hook_order.push("outer after hook") end
163
+
164
+ desc "a description of pattern_under_test"
165
+ pattern :pattern_under_test do |loom, facts|
166
+ hook_order.push "execute the pattern"
167
+ loom.do_outer_thing(hook_order)
168
+ end
169
+
170
+ module Inner
171
+ include Loom::Pattern
172
+
173
+ before do hook_order.push("inner before hook") end
174
+ after do hook_order.push("inner after hook") end
175
+
176
+ desc "an inner pattern"
177
+ pattern :an_inner_pattern do |loom, facts|
178
+ hook_order.push "execute inner pattern"
179
+ loom.do_inner_thing(hook_order)
180
+ end
181
+ end
182
+ EOS
183
+ end
184
+
185
+ it "executes outer before hooks first and after hooks last" do
186
+ pattern_under_test.call fake_shell, a_fact_set
187
+ expect(fake_shell.cmd_execution_args.first).to eql [[
188
+ "outer before hook",
189
+ "execute the pattern",
190
+ "outer after hook"
191
+ ]]
192
+ end
193
+
194
+ it "executes nested hooks in wrapped order" do
195
+ inner_pattern.call fake_shell, a_fact_set
196
+ expect(fake_shell.cmd_execution_args.first).to eql [[
197
+ "outer before hook",
198
+ "inner before hook",
199
+ "execute inner pattern",
200
+ "inner after hook",
201
+ "outer after hook"
202
+ ]]
203
+ end
204
+
205
+ end
206
+
207
+ end
@@ -0,0 +1,239 @@
1
+ require "loom/shell"
2
+ require "yaml"
3
+
4
+ describe Loom::Shell::CmdWrapper do
5
+
6
+ def unescape_cmd(cmd)
7
+ YAML.load(%Q(---\n"#{cmd.to_s}"\n))
8
+ end
9
+
10
+ context "examples" do
11
+ it "escapes spaces" do
12
+ cmd = Loom::Shell::CmdWrapper.new :printf, "print out this string"
13
+ expect(%x{#{cmd}}).to eql "print out this string"
14
+ end
15
+
16
+ it "skips escaping symbols and frozen string" do
17
+ cmd = Loom::Shell::CmdWrapper.new :"/bin/echo", :"\"yy\"", "\"xx\""
18
+ expect(%x{#{cmd}}.strip).to eql "yy \"xx\""
19
+ end
20
+ end
21
+
22
+ context ".new" do
23
+ it "does not escape whitespace between joined commands parts" do
24
+ cmd = Loom::Shell::CmdWrapper.new :echo, "-n", "\"all the rest\""
25
+ expect(cmd.escape_cmd).to eql "echo -n \\\"all\\ the\\ rest\\\""
26
+ end
27
+
28
+ it "flattens cmd input" do
29
+ cmd = Loom::Shell::CmdWrapper.new :somecmd, ["-f", "file.txt"], [:a, [:b]]
30
+ expect(cmd.to_s).to eql "somecmd -f file.txt a b"
31
+ end
32
+ end
33
+
34
+ context ".escape" do
35
+ it "escapes CmdWrappers" do
36
+ cmd = Loom::Shell::CmdWrapper.new '"hi"'
37
+ expect(Loom::Shell::CmdWrapper.escape cmd).to eql cmd.to_s
38
+ end
39
+
40
+ it "escapes recursively" do
41
+ content = 'I said "row row row your boat"'
42
+ cmd_inner = 'echo "%s"' % Loom::Shell::CmdWrapper.escape(content)
43
+ cmd_outer = '"%s"' % Loom::Shell::CmdWrapper.escape(cmd_inner)
44
+
45
+ expected = '"echo\ \"I\\\\\ said\\\\\ ' +
46
+ '\\\\\"row\\\\\ row\\\\\ row\\\\\ your\\\\\ boat\\\\\"\""'
47
+
48
+ printf_expected = 'echo\\ "I\ said\ "row\ row\ row\ your\ boat""'
49
+
50
+ expect(cmd_outer).to eql expected
51
+ expect(%x{printf #{cmd_outer}}.strip).to eql printf_expected
52
+ end
53
+ end
54
+
55
+ context "#escape" do
56
+ it "escapes strings with quotes" do
57
+ cmd = Loom::Shell::CmdWrapper.new '"hi"'
58
+
59
+ expected = '\"hi\"'
60
+ expect(cmd.escape_cmd).to eql expected
61
+ expect(%x{printf #{expected}}.strip).to eql unescape_cmd(cmd)
62
+ end
63
+
64
+ it "ignores strings without quotes" do
65
+ cmd = Loom::Shell::CmdWrapper.new 'hi'
66
+
67
+ expected = 'hi'
68
+ expect(cmd.escape_cmd).to eql expected
69
+ expect(%x{printf #{expected}}.strip).to eql unescape_cmd(cmd)
70
+ end
71
+
72
+ it "escapes nested quotes" do
73
+ cmd = Loom::Shell::CmdWrapper.new 'echo "hi"'
74
+
75
+ expected = 'echo\\ \"hi\"'
76
+ expect(cmd.escape_cmd).to eql expected
77
+ expect(%x{printf #{expected}}.strip).to eql unescape_cmd(cmd)
78
+ end
79
+
80
+ it "respects frozen? string" do
81
+ cmd = Loom::Shell::CmdWrapper.new :echo, "\"^FROZEN^\"".freeze, :"^", "^"
82
+
83
+ expected = 'echo "^FROZEN^" ^ \^'
84
+ expect(cmd.escape_cmd).to eql expected
85
+ end
86
+
87
+ it "respects wrapped frozen? strings" do
88
+ cmd_inner = Loom::Shell::CmdWrapper.new :echo, "\"^#FROZEN^\"".freeze
89
+ cmd = Loom::Shell::CmdWrapper.wrap_cmd :sudo, cmd_inner
90
+
91
+ expected = 'sudo echo "^#FROZEN^"'
92
+ expect(cmd.escape_cmd).to eql expected
93
+ end
94
+ end
95
+
96
+ # I don't want to think about this shit anymore. Don't touch this unless its
97
+ # broken.
98
+ context "#wrap" do
99
+ it "preserves escaped CmdWrappers: depth 2" do
100
+ cmd_which = Loom::Shell::CmdWrapper.new :which, :ls
101
+ cmd_sh = Loom::Shell::CmdWrapper.new :"/bin/sh", "-c", should_quote: true
102
+
103
+ composed_cmd = cmd_sh.wrap(cmd_sh.wrap(cmd_which)).to_s
104
+
105
+ expected = "/bin/sh -c \"/bin/sh -c \\\"which ls\\\"\""
106
+
107
+ expect(composed_cmd).to eql expected
108
+ # Matches /bin/ls$ for portability, on fedora it's /usr/bin/ls
109
+ expect(%x{#{composed_cmd}}.strip).to match /\/bin\/ls$/
110
+ end
111
+
112
+ it "preserves escaped CmdWrappers: depth N" do
113
+ cmd_which = Loom::Shell::CmdWrapper.new :which, :ls
114
+ cmd_sh = Loom::Shell::CmdWrapper.new :"/bin/sh", "-c", should_quote: true
115
+
116
+ composed_cmd = cmd_sh.wrap(cmd_sh.wrap(cmd_sh.wrap(cmd_which))).to_s
117
+
118
+ expected =
119
+ "/bin/sh -c \"/bin/sh -c \\\"/bin/sh -c \\\\\\\"which ls\\\\\\\"\\\"\""
120
+
121
+ expect(composed_cmd).to eql expected
122
+ # Matches /bin/ls$ for portability, on fedora it's /usr/bin/ls
123
+ expect(%x{#{composed_cmd}}.strip).to match /\/bin\/ls$/
124
+ end
125
+ end
126
+
127
+ context Loom::Shell::CmdRedirect do
128
+
129
+ let(:cmd_parts) { [:"/bin/ls"] }
130
+
131
+ it "redirects stdout to file" do
132
+ redirect = Loom::Shell::CmdRedirect.new "/my/file"
133
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
134
+
135
+ expect(cmd.to_s).to eql "/bin/ls >/my/file"
136
+ end
137
+
138
+ it "redirects stderr to file" do
139
+ redirect = Loom::Shell::CmdRedirect.new "/my/file", fd: 2
140
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
141
+
142
+ expect(cmd.to_s).to eql "/bin/ls 2>/my/file"
143
+ end
144
+
145
+ it "appends stdout to file" do
146
+ mode = Loom::Shell::CmdRedirect::Mode::APPEND
147
+
148
+ redirect = Loom::Shell::CmdRedirect.new "/my/file", mode: mode
149
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
150
+
151
+ expect(cmd.to_s).to eql "/bin/ls >>/my/file"
152
+ end
153
+
154
+ it "appends stderr to file" do
155
+ mode = Loom::Shell::CmdRedirect::Mode::APPEND
156
+
157
+ redirect = Loom::Shell::CmdRedirect.new "/my/file", fd: 2, mode: mode
158
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
159
+
160
+ expect(cmd.to_s).to eql "/bin/ls 2>>/my/file"
161
+ end
162
+
163
+ it "redirects stderr to stdout" do
164
+ redirect = Loom::Shell::CmdRedirect.new 1, fd: 2
165
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
166
+
167
+ expect(cmd.to_s).to eql "/bin/ls 2>1"
168
+ end
169
+
170
+ it "redirects both stderr and stdout to file" do
171
+ mode = Loom::Shell::CmdRedirect::Mode::OUTPUT_12
172
+
173
+ redirect = Loom::Shell::CmdRedirect.new "/my/file", mode: mode
174
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
175
+
176
+ expect(cmd.to_s).to eql "/bin/ls &>/my/file"
177
+ end
178
+
179
+ context "helper factories" do
180
+
181
+ it "appends to stdout" do
182
+ redirect = Loom::Shell::CmdRedirect.append_stdout "/my/file"
183
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirect
184
+
185
+ expect(cmd.to_s).to eql "/bin/ls >>/my/file"
186
+ end
187
+ end
188
+
189
+ context "multiple redirects" do
190
+ it "redirects both stderr and stdout to file" do
191
+
192
+ redirects = [
193
+ Loom::Shell::CmdRedirect.new("/my/file"),
194
+ Loom::Shell::CmdRedirect.new(1, fd: 2)
195
+ ]
196
+ cmd = Loom::Shell::CmdWrapper.new *cmd_parts, redirect: redirects
197
+
198
+ expect(cmd.to_s).to eql "/bin/ls >/my/file 2>1"
199
+ end
200
+ end
201
+
202
+ context "wrapped redirects" do
203
+ it "does not escape redirects" do
204
+ skip "fails - this is why I started implementing the harness see " +
205
+ "`git show 1196d2ec2` for info"
206
+ r = Loom::Shell::CmdRedirect.append_stdout "/my/file"
207
+
208
+ cmd_inner = Loom::Shell::CmdWrapper.new :echo, :hello, redirect: r
209
+ cmd = Loom::Shell::CmdWrapper.wrap_cmd :sudo, "-u", :root, cmd_inner
210
+
211
+ expect(cmd.to_s).to eql "sudo -u root echo hello >>/my/file"
212
+ end
213
+ end
214
+ end
215
+
216
+ context Loom::Shell::CmdPipeline do
217
+
218
+ let(:cmds) do
219
+ [
220
+ Loom::Shell::CmdWrapper.new(:find, ".", "-name", "*foo", "-print0"),
221
+ Loom::Shell::CmdWrapper.new(:xargs, "-0", "-I-", "ls", "-")
222
+ ]
223
+ end
224
+
225
+ it "pipes commands together" do
226
+ pipeline = Loom::Shell::CmdPipeline.new cmds
227
+
228
+ expected = "find . -name \\*foo -print0 | xargs -0 -I- ls -"
229
+ expect(pipeline.to_s).to eql expected
230
+ end
231
+
232
+ it "accepts commands as pre-escaped strings" do
233
+ pipeline = Loom::Shell::CmdPipeline.new ["I'm already escaped", "me too!"]
234
+
235
+ expected = "I'm already escaped | me too!"
236
+ expect(pipeline.to_s).to eql expected
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,42 @@
1
+ describe Loom::Shell::HarnessBlob do
2
+
3
+ HARNESS = "./scripts/harness.sh"
4
+
5
+ # Set debug_script to true in a context to see STDERR debugging.
6
+ let(:debug_script) { false }
7
+
8
+ let(:cmd) { 'echo hi there | (echo "from subshell"; cat);' }
9
+ let(:encoded_script) do
10
+ run_harnes_script "--print_base64", :stdin => cmd
11
+ end
12
+ let(:golden_checksum) do
13
+ run_harnes_script "--print_checksum", encoded_script
14
+ end
15
+
16
+
17
+ def run_harnes_script(cmd, *args, stdin: nil)
18
+ cmd = %Q{./scripts/harness.sh 2>/dev/null #{cmd}}
19
+
20
+ heredoc = nil
21
+ if stdin
22
+ heredoc = "<<'EOS'\n#{stdin}\nEOS"
23
+ cmd << " - #{args.join " "} #{heredoc}"
24
+ else
25
+ cmd << " #{args.join " "}"
26
+ end
27
+
28
+ %x{#{cmd}}
29
+ end
30
+
31
+ describe "scripts/harness.sh parity" do
32
+
33
+ subject { Loom::Shell::HarnessBlob.new cmd }
34
+
35
+ it "computes a valid script checksum" do
36
+ run_harnes_script "--check", golden_checksum, {
37
+ :stdin => subject.encoded_script
38
+ }
39
+ expect($?.exitstatus).to eq 0
40
+ end
41
+ end
42
+ end