dk 0.0.1 → 0.1.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 (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