loom-core 0.0.1

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