dk 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +643 -1
  3. data/bin/dk +7 -0
  4. data/dk.gemspec +7 -3
  5. data/lib/dk/ansi.rb +98 -0
  6. data/lib/dk/cli.rb +173 -0
  7. data/lib/dk/config.rb +217 -0
  8. data/lib/dk/config_runner.rb +24 -0
  9. data/lib/dk/dk_runner.rb +13 -0
  10. data/lib/dk/dry_runner.rb +43 -0
  11. data/lib/dk/has_set_param.rb +42 -0
  12. data/lib/dk/has_ssh_opts.rb +36 -0
  13. data/lib/dk/has_the_runs.rb +23 -0
  14. data/lib/dk/has_the_stubs.rb +116 -0
  15. data/lib/dk/local.rb +84 -0
  16. data/lib/dk/null_logger.rb +13 -0
  17. data/lib/dk/remote.rb +132 -0
  18. data/lib/dk/runner.rb +202 -0
  19. data/lib/dk/task.rb +266 -0
  20. data/lib/dk/task_run.rb +17 -0
  21. data/lib/dk/test_runner.rb +54 -0
  22. data/lib/dk/tree_runner.rb +64 -0
  23. data/lib/dk/version.rb +1 -1
  24. data/lib/dk.rb +23 -1
  25. data/test/helper.rb +6 -1
  26. data/test/support/config/dk.rb +7 -0
  27. data/test/support/config/task_defs.rb +10 -0
  28. data/test/support/factory.rb +38 -0
  29. data/test/support/log/.gitkeep +0 -0
  30. data/test/system/has_the_stubs_tests.rb +355 -0
  31. data/test/system/runner_tests.rb +222 -0
  32. data/test/unit/ansi_tests.rb +40 -0
  33. data/test/unit/cli_tests.rb +317 -0
  34. data/test/unit/config_runner_tests.rb +60 -0
  35. data/test/unit/config_tests.rb +427 -0
  36. data/test/unit/dk_runner_tests.rb +34 -0
  37. data/test/unit/dk_tests.rb +49 -0
  38. data/test/unit/dry_runner_tests.rb +71 -0
  39. data/test/unit/has_set_param_tests.rb +46 -0
  40. data/test/unit/has_ssh_opts_tests.rb +81 -0
  41. data/test/unit/has_the_runs_tests.rb +37 -0
  42. data/test/unit/has_the_stubs_tests.rb +279 -0
  43. data/test/unit/local_tests.rb +174 -0
  44. data/test/unit/null_logger_tests.rb +17 -0
  45. data/test/unit/remote_tests.rb +330 -0
  46. data/test/unit/runner_tests.rb +398 -0
  47. data/test/unit/task_run_tests.rb +40 -0
  48. data/test/unit/task_tests.rb +943 -0
  49. data/test/unit/test_runner_tests.rb +189 -0
  50. data/test/unit/tree_runner_tests.rb +152 -0
  51. metadata +106 -9
data/lib/dk/task.rb ADDED
@@ -0,0 +1,266 @@
1
+ require 'much-plugin'
2
+ require 'dk'
3
+ require 'dk/remote'
4
+ require 'dk/runner'
5
+
6
+ module Dk
7
+
8
+ module Task
9
+ include MuchPlugin
10
+
11
+ class ParamsHash < Hash
12
+ def try_param(key)
13
+ begin; self.[](key); rescue Dk::NoParamError; nil; end
14
+ end
15
+ end
16
+
17
+ plugin_included do
18
+ include InstanceMethods
19
+ extend ClassMethods
20
+
21
+ end
22
+
23
+ module InstanceMethods
24
+
25
+ def initialize(runner, params = nil)
26
+ params ||= {}
27
+ @dk_runner = runner
28
+ @dk_params = ParamsHash.new{ |h, k| @dk_runner.params[k] }
29
+ @dk_params.merge!(params)
30
+ end
31
+
32
+ def dk_run
33
+ self.dk_run_callbacks 'before'
34
+ @dk_runner.log_task_run(self.class) do
35
+ catch(:halt){ self.run! }
36
+ end
37
+ self.dk_run_callbacks 'after'
38
+ end
39
+
40
+ def run!
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def dk_run_callbacks(named)
45
+ callbacks = CallbackSet.new
46
+ callbacks.add_callbacks(@dk_runner.task_callbacks("prepend_#{named}", self.class))
47
+ callbacks.add_callbacks(self.class.send("#{named}_callbacks") || [])
48
+ callbacks.add_callbacks(@dk_runner.task_callbacks(named, self.class))
49
+ callbacks.each{ |c| run_task(c.task_class, c.params) }
50
+ end
51
+
52
+ def dk_dsl_ssh_hosts
53
+ @dk_dsl_ssh_hosts ||= self.instance_eval(&self.class.ssh_hosts)
54
+ end
55
+
56
+ def ==(other_task)
57
+ self.class == other_task.class
58
+ end
59
+
60
+ private
61
+
62
+ # Helpers
63
+
64
+ def run_task(task_class, params = nil)
65
+ @dk_runner.run_task(task_class, params)
66
+ end
67
+
68
+ def cmd(cmd_str, *args)
69
+ given_opts = args.last.kind_of?(::Hash) ? args.pop : nil
70
+ input = args.last
71
+ @dk_runner.cmd(self, cmd_str, input, given_opts)
72
+ end
73
+
74
+ def cmd!(cmd_str, *args)
75
+ cmd = cmd(cmd_str, *args)
76
+ if !cmd.success?
77
+ raise CmdRunError, "error running `#{cmd.cmd_str}`", caller
78
+ end
79
+ cmd
80
+ end
81
+
82
+ def ssh(cmd_str, *args)
83
+ given_opts = args.last.kind_of?(::Hash) ? args.pop : nil
84
+ input = args.last
85
+ @dk_runner.ssh(self, cmd_str, input, given_opts, dk_build_ssh_opts(given_opts))
86
+ end
87
+
88
+ def ssh!(cmd_str, *args)
89
+ cmd = ssh(cmd_str, *args)
90
+ if !cmd.success?
91
+ raise SSHRunError, "error running `#{cmd.cmd_str}` over ssh", caller
92
+ end
93
+ cmd
94
+ end
95
+
96
+ def params
97
+ @dk_params
98
+ end
99
+
100
+ def set_param(key, value)
101
+ @dk_runner.set_param(key, value)
102
+ end
103
+
104
+ def param?(key)
105
+ @dk_params.key?(key) || @dk_runner.params.key?(key)
106
+ end
107
+
108
+ def try_param(key)
109
+ @dk_params.try_param(key)
110
+ end
111
+
112
+ def before(subject, callback, params = nil)
113
+ @dk_runner.add_task_callback('before', subject, callback, params)
114
+ end
115
+
116
+ def prepend_before(subject, callback, params = nil)
117
+ @dk_runner.add_task_callback('prepend_before', subject, callback, params)
118
+ end
119
+
120
+ def after(subject, callback, params = nil)
121
+ @dk_runner.add_task_callback('after', subject, callback, params)
122
+ end
123
+
124
+ def prepend_after(subject, callback, params = nil)
125
+ @dk_runner.add_task_callback('prepend_after', subject, callback, params)
126
+ end
127
+
128
+ def ssh_hosts(group_name = nil, *values)
129
+ @dk_runner.ssh_hosts(group_name, *values)
130
+ end
131
+
132
+ def ssh_cmd_str(cmd_str, opts = nil)
133
+ opts ||= {}
134
+ Remote.ssh_cmd_str(
135
+ cmd_str,
136
+ opts[:host].to_s,
137
+ dk_lookup_ssh_args(opts[:ssh_args]),
138
+ dk_lookup_host_ssh_args(opts[:host_ssh_args])
139
+ )
140
+ end
141
+
142
+ def halt
143
+ throw :halt
144
+ end
145
+
146
+ def log_info(msg, *styles); @dk_runner.log_info(msg, *styles); end
147
+ def log_debug(msg, *styles); @dk_runner.log_debug(msg, *styles); end
148
+ def log_error(msg, *styles); @dk_runner.log_error(msg, *styles); end
149
+
150
+ def dk_build_ssh_opts(opts)
151
+ opts ||= {}
152
+ opts.merge({
153
+ :hosts => dk_lookup_ssh_hosts(opts[:hosts]),
154
+ :ssh_args => dk_lookup_ssh_args(opts[:ssh_args]),
155
+ :host_ssh_args => dk_lookup_host_ssh_args(opts[:host_ssh_args])
156
+ })
157
+ end
158
+
159
+ def dk_lookup_ssh_hosts(opts_hosts)
160
+ [*(
161
+ ssh_hosts[opts_hosts] ||
162
+ opts_hosts ||
163
+ ssh_hosts[self.dk_dsl_ssh_hosts] ||
164
+ self.dk_dsl_ssh_hosts
165
+ )]
166
+ end
167
+
168
+ def dk_lookup_ssh_args(opts_args)
169
+ opts_args || @dk_runner.ssh_args
170
+ end
171
+
172
+ def dk_lookup_host_ssh_args(opts_args)
173
+ opts_args || @dk_runner.host_ssh_args
174
+ end
175
+
176
+ end
177
+
178
+ CmdRunError = Class.new(RuntimeError)
179
+ SSHRunError = Class.new(RuntimeError)
180
+
181
+ module ClassMethods
182
+
183
+ def description(value = nil)
184
+ @description = value.to_s if !value.nil?
185
+ @description
186
+ end
187
+ alias_method :desc, :description
188
+
189
+ def before_callbacks; @before_callbacks ||= CallbackSet.new; end
190
+ def after_callbacks; @after_callbacks ||= CallbackSet.new; end
191
+
192
+ def before_callback_task_classes; self.before_callbacks.map(&:task_class); end
193
+ def after_callback_task_classes; self.after_callbacks.map(&:task_class); end
194
+
195
+ def before(task_class, params = nil)
196
+ self.before_callbacks << Callback.new(task_class, params)
197
+ end
198
+
199
+ def after(task_class, params = nil)
200
+ self.after_callbacks << Callback.new(task_class, params)
201
+ end
202
+
203
+ def prepend_before(task_class, params = nil)
204
+ self.before_callbacks.unshift(Callback.new(task_class, params))
205
+ end
206
+
207
+ def prepend_after(task_class, params = nil)
208
+ self.after_callbacks.unshift(Callback.new(task_class, params))
209
+ end
210
+
211
+ def ssh_hosts(value = nil, &block)
212
+ @ssh_hosts = block || proc{ value } if !block.nil? || !value.nil?
213
+ @ssh_hosts || proc{}
214
+ end
215
+
216
+ def run_only_once(value = nil)
217
+ @run_only_once = !!value if !value.nil?
218
+ @run_only_once
219
+ end
220
+
221
+ end
222
+
223
+ Callback = Struct.new(:task_class, :params)
224
+
225
+ class CallbackSet < ::Array
226
+
227
+ def <<(callback); super unless self.include?(callback); end
228
+ def unshift(callback); super unless self.include?(callback); end
229
+
230
+ def add_callbacks(callbacks)
231
+ callbacks.each{ |cb| self.<<(cb) }
232
+ end
233
+
234
+ end
235
+
236
+ module TestHelpers
237
+ include MuchPlugin
238
+
239
+ plugin_included do
240
+ require 'dk/test_runner'
241
+ include InstanceMethods
242
+ end
243
+
244
+ module InstanceMethods
245
+
246
+ def test_runner(task_class, args = nil)
247
+ Dk::TestRunner.new(args).tap do |runner|
248
+ runner.task_class = task_class
249
+ end
250
+ end
251
+
252
+ def test_task(task_class, args = nil)
253
+ test_runner(task_class, args).task
254
+ end
255
+
256
+ def ssh_cmd_str(task, *args)
257
+ task.instance_eval{ ssh_cmd_str(*args) }
258
+ end
259
+
260
+ end
261
+
262
+ end
263
+
264
+ end
265
+
266
+ end
@@ -0,0 +1,17 @@
1
+ require 'dk/has_the_runs'
2
+
3
+ module Dk
4
+
5
+ class TaskRun
6
+ include HasTheRuns
7
+
8
+ attr_reader :task_class, :params
9
+
10
+ def initialize(task_class, params)
11
+ @task_class = task_class
12
+ @params = params
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,54 @@
1
+ require 'dk/has_the_runs'
2
+ require 'dk/has_the_stubs'
3
+ require 'dk/local'
4
+ require 'dk/remote'
5
+ require 'dk/runner'
6
+ require 'dk/task_run'
7
+
8
+ module Dk
9
+
10
+ class TestRunner < Runner
11
+ include HasTheRuns
12
+ include HasTheStubs
13
+
14
+ attr_accessor :task_class
15
+
16
+ # test runners are designed to only run their task
17
+ def run(params = nil)
18
+ self.task(params).tap(&:dk_run)
19
+ end
20
+
21
+ # don't run any sub-tasks, just track that a sub-task was run
22
+ def run_task(task_class, params = nil)
23
+ TaskRun.new(task_class, params).tap{ |tr| self.runs << tr }
24
+ end
25
+
26
+ # track that a local cmd was run
27
+ def cmd(task, cmd_str, input, given_opts)
28
+ super(task, cmd_str, input, given_opts).tap{ |c| self.runs << c }
29
+ end
30
+
31
+ # track that a remote cmd was run
32
+ def ssh(task, cmd_str, input, given_opts, ssh_opts)
33
+ super(task, cmd_str, input, given_opts, ssh_opts).tap{ |c| self.runs << c }
34
+ end
35
+
36
+ # test task API
37
+
38
+ def task(params = nil)
39
+ @task ||= build_task(self.task_class, params)
40
+ end
41
+
42
+ private
43
+
44
+ def has_the_stubs_build_local_cmd(cmd_str, given_opts)
45
+ Local::CmdSpy.new(cmd_str, given_opts)
46
+ end
47
+
48
+ def has_the_stubs_build_remote_cmd(cmd_str, ssh_opts)
49
+ Remote::CmdSpy.new(cmd_str, ssh_opts)
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,64 @@
1
+ require 'dk/dry_runner'
2
+ require 'dk/has_the_runs'
3
+ require 'dk/null_logger'
4
+ require 'dk/task_run'
5
+
6
+ module Dk
7
+
8
+ class TreeRunner < DryRunner
9
+ include HasTheRuns
10
+
11
+ LEVEL_PREFIX = ' '.freeze
12
+ LEVEL_BULLET = '|-- '.freeze
13
+
14
+ def initialize(config, kernel)
15
+ super(config, :logger => NullLogger.new) # disable any logging
16
+
17
+ @task_run_stack = [self]
18
+ @run_num = 0
19
+ @kernel = kernel
20
+ end
21
+
22
+ def run(*args)
23
+ # wipe the task runs before every run; that way `output_task_runs` outputs
24
+ # just this run's task runs
25
+ self.runs.clear
26
+
27
+ # increment the run num and run the task
28
+ @run_num += 1
29
+ task = super
30
+
31
+ # recursively output the task runs in a tree format
32
+ output_task_runs(self.runs, 0, "#{@run_num}) ".rjust(LEVEL_PREFIX.size, ' '))
33
+
34
+ # return the top-level task that was run
35
+ task
36
+ end
37
+
38
+ private
39
+
40
+ # track all task runs
41
+ def build_and_run_task(task_class, params = nil)
42
+ task_run = TaskRun.new(task_class, params)
43
+ @task_run_stack.last.runs << task_run
44
+
45
+ @task_run_stack.push(task_run)
46
+ task = super(task_class, params)
47
+ @task_run_stack.pop
48
+ task
49
+ end
50
+
51
+ def output_task_runs(runs, level, prefix = nil)
52
+ runs.each do |task_run|
53
+ # recursively output the prefix and task class on indented, bulleted lines
54
+ @kernel.puts "#{LEVEL_PREFIX*level}" \
55
+ "#{LEVEL_BULLET if level > 0}" \
56
+ "#{prefix}#{task_run.task_class}"
57
+ output_task_runs(task_run.runs, level+1)
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
data/lib/dk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dk
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/dk.rb CHANGED
@@ -1,4 +1,26 @@
1
- require "dk/version"
1
+ require 'dk/version'
2
+ require 'dk/config'
3
+ require 'dk/task'
2
4
 
3
5
  module Dk
6
+
7
+ def self.config
8
+ @config ||= Config.new
9
+ end
10
+
11
+ def self.configure(&block)
12
+ self.config.init_procs << block
13
+ end
14
+
15
+ def self.init
16
+ self.config.init
17
+ end
18
+
19
+ def self.reset
20
+ @config = Config.new
21
+ end
22
+
23
+ NoticeError = Class.new(RuntimeError)
24
+ NoParamError = Class.new(ArgumentError)
25
+
4
26
  end
data/test/helper.rb CHANGED
@@ -2,13 +2,18 @@
2
2
  # put any test helpers here
3
3
 
4
4
  # add the root dir to the load path
5
- $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
5
+ require 'pathname'
6
+ ROOT_PATH = Pathname.new(File.expand_path("../..", __FILE__))
7
+ $LOAD_PATH.unshift(ROOT_PATH)
6
8
 
7
9
  # require pry for debugging (`binding.pry`)
8
10
  require 'pry'
9
11
 
10
12
  require 'test/support/factory'
11
13
 
14
+ # put scmd in test mode
15
+ ENV['SCMD_TEST_MODE'] = '1'
16
+
12
17
  # 1.8.7 backfills
13
18
 
14
19
  # Array#sample
@@ -0,0 +1,7 @@
1
+ require 'dk'
2
+ require 'test/support/config/task_defs'
3
+
4
+ Dk.configure do
5
+ task 'cli-test-task', CLITestTask
6
+ task 'cli-other-task', CLIOtherTask
7
+ end
@@ -0,0 +1,10 @@
1
+ class CLITestTask
2
+ include Dk::Task
3
+
4
+ end
5
+
6
+ class CLIOtherTask
7
+ include Dk::Task
8
+
9
+ end
10
+
@@ -3,4 +3,42 @@ require 'assert/factory'
3
3
  module Factory
4
4
  extend Assert::Factory
5
5
 
6
+ def self.ssh_cmd_opts
7
+ cmd_opts = { Factory.string => Factory.string }
8
+ # optionally have these options, this will ensure the default ssh cmd opts
9
+ # are used if these aren't provided
10
+ if Factory.boolean
11
+ cmd_opts.merge!({
12
+ :hosts => Factory.hosts,
13
+ :ssh_args => Factory.string,
14
+ :host_ssh_args => { Factory.string => Factory.string }
15
+ })
16
+ end
17
+ cmd_opts
18
+ end
19
+
20
+ def self.stdout
21
+ Factory.integer(3).times.map{ Factory.string }.join("\n")
22
+ end
23
+
24
+ def self.stderr
25
+ Factory.integer(3).times.map{ Factory.string }.join("\n")
26
+ end
27
+
28
+ def self.exitstatus
29
+ [0, 1].sample
30
+ end
31
+
32
+ def self.hosts
33
+ Factory.integer(3).times.map{ "#{Factory.string}.example.com" }
34
+ end
35
+
36
+ def self.log_file
37
+ ROOT_PATH.join("test/support/log/#{Factory.string}.txt")
38
+ end
39
+
40
+ def self.task_callback(task_class, params = nil)
41
+ Dk::Task::Callback.new(task_class, params || {})
42
+ end
43
+
6
44
  end
File without changes