kindly 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +31 -0
- data/kindly.gemspec +3 -1
- data/lib/kindly/db.rb +150 -0
- data/lib/kindly/jobs/do_nothing.rb +15 -0
- data/lib/kindly/jobs/test_job.rb +15 -0
- data/lib/kindly/jobs/test_job_with_input.rb +17 -0
- data/lib/kindly/jobs/test_job_with_output.rb +17 -0
- data/lib/kindly/queue.rb +56 -0
- data/lib/kindly/registry.rb +30 -0
- data/lib/kindly/requester.rb +17 -0
- data/lib/kindly/runner.rb +43 -16
- data/lib/kindly/version.rb +1 -1
- data/lib/kindly.rb +13 -28
- data/test/fixtures/jobs/fail.rb +15 -0
- data/test/kindly/migration_test.rb +114 -108
- data/test/kindly/registry_test.rb +24 -0
- data/test/kindly/runner_test.rb +68 -35
- data/test/kindly_test.rb +50 -17
- metadata +45 -16
- data/bin/kindly +0 -23
- data/lib/kindly/handlers/do_nothing.rb +0 -19
- data/lib/kindly/handlers.rb +0 -29
- data/lib/kindly/migration.rb +0 -37
- data/test/fixtures/pending/one.json +0 -3
- data/test/fixtures/pending/two.json +0 -3
- data/test/kindly/handlers_test.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e6a32b5242cdaa718ac34d75b5f0d4c216faa09
|
4
|
+
data.tar.gz: 6b9b08dc1282bd73a313e2841028eecaacc19433
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62eabc59ec779bed01bbb72a4501d5b1492d9968dcfa7a70cd473558ac75863fe2fdf6a72d143ead6a590eac236e667e5b06ec90358444c6570394f6e6d5a87a
|
7
|
+
data.tar.gz: a3447e56fd04edef3ec992f85d362ad651a970b08291907367f18ed49ac1272c9b827a82042a46fd8866228f6d840518362da04127d9b81c627558d391e72388
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rake/testtask'
|
3
|
+
require 'kindly'
|
3
4
|
|
4
5
|
Rake::TestTask.new do |test|
|
5
6
|
test.libs << 'lib' << 'test'
|
@@ -7,4 +8,34 @@ Rake::TestTask.new do |test|
|
|
7
8
|
test.verbose = true
|
8
9
|
end
|
9
10
|
|
11
|
+
desc 'Request test_job'
|
12
|
+
task :request_test_job do
|
13
|
+
Kindly.request :test_job
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Request test_job_with_input'
|
17
|
+
task :request_test_job_with_input do
|
18
|
+
Kindly.request :test_job_with_input, 'hello'
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Request test_job_with_output'
|
22
|
+
task :request_test_job_with_output do
|
23
|
+
Kindly.request :test_job_with_output
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Run test_job'
|
27
|
+
task :run_test_job do
|
28
|
+
Kindly.run :test_job
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Run test_job_with_input'
|
32
|
+
task :run_test_job_with_input do
|
33
|
+
Kindly.run :test_job_with_input
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Run test_job_with_output'
|
37
|
+
task :run_test_job_with_output do
|
38
|
+
Kindly.run :test_job_with_output
|
39
|
+
end
|
40
|
+
|
10
41
|
task :default => :test
|
data/kindly.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.version = Kindly::VERSION
|
10
10
|
spec.authors = ['Greg Scott']
|
11
11
|
spec.email = ['i@gregoryjscott.com']
|
12
|
-
spec.summary = %q{Kindly run
|
12
|
+
spec.summary = %q{Kindly run jobs of any kind.}
|
13
13
|
spec.homepage = 'https://github.com/gregoryjscott/kindly'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
@@ -19,9 +19,11 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'mercenary', '~> 0.3'
|
22
|
+
spec.add_runtime_dependency 'aws-sdk', '~> 2'
|
22
23
|
|
23
24
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
24
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
25
26
|
spec.add_development_dependency 'mocha', '~> 1.1'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.8'
|
26
28
|
|
27
29
|
end
|
data/lib/kindly/db.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'kindly'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
module Kindly
|
5
|
+
class DB
|
6
|
+
|
7
|
+
DEFAULTS = {
|
8
|
+
:table_names => {
|
9
|
+
:data => 'job-data',
|
10
|
+
:pending => 'job-pending',
|
11
|
+
:running => 'job-running',
|
12
|
+
:completed => 'job-completed',
|
13
|
+
:failed => 'job-failed'
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@config = DEFAULTS.merge(options)
|
19
|
+
@db = Aws::DynamoDB::Client.new(region: 'us-west-2')
|
20
|
+
end
|
21
|
+
|
22
|
+
def user
|
23
|
+
@user ||= Aws::IAM::CurrentUser.new(region: 'us-west-2')
|
24
|
+
end
|
25
|
+
|
26
|
+
def insert_job(job_name, input)
|
27
|
+
item = {
|
28
|
+
'JobId' => SecureRandom.uuid,
|
29
|
+
'JobName' => job_name,
|
30
|
+
'RequestedAt' => Time.now.to_s,
|
31
|
+
'RequestedBy' => user.user_name
|
32
|
+
}
|
33
|
+
|
34
|
+
unless input.empty?
|
35
|
+
data = insert_job_data(input)
|
36
|
+
item['InputDataId'] = data['JobDataId']
|
37
|
+
end
|
38
|
+
|
39
|
+
@db.put_item({ table_name: 'job-pending', item: item })
|
40
|
+
item
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_job_data(data)
|
44
|
+
item = {
|
45
|
+
'JobDataId' => SecureRandom.uuid,
|
46
|
+
'Data' => data,
|
47
|
+
'CreatedAt' => Time.now.to_s
|
48
|
+
}
|
49
|
+
@db.put_item({ table_name: 'job-data', item: item })
|
50
|
+
item
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_job(job_name, job_id)
|
54
|
+
job = Registry.find(job_name)
|
55
|
+
fields = fetch_job_fields(job_id)
|
56
|
+
add_fields_to_job(job, fields)
|
57
|
+
|
58
|
+
if job.fields.has_key?('InputDataId')
|
59
|
+
input = fetch_job_data(job.fields['InputDataId'])
|
60
|
+
job.input = input
|
61
|
+
end
|
62
|
+
job
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_job_status(job, job_status)
|
66
|
+
case job_status
|
67
|
+
when :running
|
68
|
+
delete_job_status(job, :pending)
|
69
|
+
when :completed, :failed
|
70
|
+
delete_job_status(job, :running)
|
71
|
+
else
|
72
|
+
raise "#{new_status} is invalid for job #{job.fields['JobId']}."
|
73
|
+
end
|
74
|
+
insert_job_status(job, job_status)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def fetch_job_fields(job_id)
|
80
|
+
response = @db.scan({
|
81
|
+
table_name: @config[:table_names][:pending],
|
82
|
+
select: 'ALL_ATTRIBUTES',
|
83
|
+
filter_expression: "JobId = :job_id",
|
84
|
+
expression_attribute_values: { ":job_id": job_id }
|
85
|
+
})
|
86
|
+
|
87
|
+
raise no_jobs(job_id) if response.items.length == 0
|
88
|
+
raise too_many_jobs(job_id) if response.items.length > 1
|
89
|
+
response.items[0]
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_job_data(job_data_id)
|
93
|
+
response = @db.scan({
|
94
|
+
table_name: @config[:table_names][:data],
|
95
|
+
select: 'ALL_ATTRIBUTES',
|
96
|
+
filter_expression: "JobDataId = :job_data_id",
|
97
|
+
expression_attribute_values: { ":job_data_id": job_data_id }
|
98
|
+
})
|
99
|
+
|
100
|
+
raise no_job_data(job_data_id) if response.items.length == 0
|
101
|
+
raise too_many_job_data(job_data_id) if response.items.length > 1
|
102
|
+
response.items[0]['Data']
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_fields_to_job(job, fields)
|
106
|
+
job.instance_eval do
|
107
|
+
def fields
|
108
|
+
@fields
|
109
|
+
end
|
110
|
+
|
111
|
+
def fields=(val)
|
112
|
+
@fields = val
|
113
|
+
end
|
114
|
+
end
|
115
|
+
job.fields = fields
|
116
|
+
end
|
117
|
+
|
118
|
+
def insert_job_status(job, job_status)
|
119
|
+
job.fields['CreatedAt'] = Time.now.to_s
|
120
|
+
@db.put_item({
|
121
|
+
table_name: @config[:table_names][job_status],
|
122
|
+
item: job.fields
|
123
|
+
})
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete_job_status(job, job_status)
|
127
|
+
@db.delete_item({
|
128
|
+
table_name: @config[:table_names][job_status],
|
129
|
+
key: { 'JobId': job.fields['JobId'] }
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
def no_jobs(job_id)
|
134
|
+
"No pending jobs found for #{job_id}."
|
135
|
+
end
|
136
|
+
|
137
|
+
def too_many_jobs(job_id)
|
138
|
+
"Found too many pending job records for #{job_id}."
|
139
|
+
end
|
140
|
+
|
141
|
+
def no_job_data(job_data_id)
|
142
|
+
"No job data found for #{job_data_id}."
|
143
|
+
end
|
144
|
+
|
145
|
+
def too_many_job_data(job_data_id)
|
146
|
+
"Found too many job data records for #{job_data_id}."
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
data/lib/kindly/queue.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'kindly'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
module Kindly
|
5
|
+
class Queue
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@sqs = Aws::SQS::Client.new(region: 'us-west-2')
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(job_name, job_id)
|
12
|
+
@sqs.send_message({
|
13
|
+
queue_url: queue_url(job_name),
|
14
|
+
message_body: "#{@job_name} has been requested.",
|
15
|
+
message_attributes: {
|
16
|
+
'JobId' => {
|
17
|
+
string_value: job_id,
|
18
|
+
data_type: "String",
|
19
|
+
},
|
20
|
+
},
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def pop(job_name)
|
25
|
+
response = @sqs.receive_message({
|
26
|
+
queue_url: queue_url(job_name),
|
27
|
+
message_attribute_names: ['JobId'],
|
28
|
+
max_number_of_messages: 1
|
29
|
+
})
|
30
|
+
raise too_many_messages(job_name) if response.messages.length > 1
|
31
|
+
return nil if response.messages.length == 0
|
32
|
+
|
33
|
+
message = response.messages[0]
|
34
|
+
job_id = message.message_attributes['JobId'].string_value
|
35
|
+
|
36
|
+
@sqs.delete_message({
|
37
|
+
queue_url: queue_url(job_name),
|
38
|
+
receipt_handle: message.receipt_handle
|
39
|
+
})
|
40
|
+
|
41
|
+
job_id
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def queue_url(job_name)
|
47
|
+
queue = job_name.to_s.gsub('_', '-')
|
48
|
+
"https://sqs.us-west-2.amazonaws.com/529271381487/#{queue}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def too_many_messages(job_name)
|
52
|
+
"Found too many messages for #{job_name}."
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'kindly'
|
2
|
+
|
3
|
+
module Kindly
|
4
|
+
module Registry
|
5
|
+
|
6
|
+
def self.register(job_name, job)
|
7
|
+
@@jobs ||= {}
|
8
|
+
@@jobs[job_name.to_sym] = job
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.unregister(job_name)
|
12
|
+
@@jobs.delete(job_name.to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find(job_name)
|
16
|
+
raise no_job(job_name) unless registered?(job_name)
|
17
|
+
@@jobs[job_name.to_sym]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.no_job(job_name)
|
23
|
+
"No job registered with name #{job_name}."
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.registered?(job_name)
|
27
|
+
@@jobs.has_key?(job_name.to_sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'kindly'
|
2
|
+
|
3
|
+
module Kindly
|
4
|
+
class Requester
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@queue = Queue.new
|
8
|
+
@db = DB.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def request(job_name, input = {})
|
12
|
+
job = @db.insert_job(job_name, input)
|
13
|
+
@queue.add(job_name, job['JobId'])
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/kindly/runner.rb
CHANGED
@@ -3,28 +3,60 @@ require 'kindly'
|
|
3
3
|
module Kindly
|
4
4
|
class Runner
|
5
5
|
|
6
|
-
def initialize
|
7
|
-
@
|
6
|
+
def initialize
|
7
|
+
@queue = Queue.new
|
8
|
+
@db = DB.new
|
8
9
|
end
|
9
10
|
|
10
|
-
def run(
|
11
|
-
|
11
|
+
def run(job_name)
|
12
|
+
job_id = @queue.pop(job_name)
|
13
|
+
if job_id.nil?
|
14
|
+
puts "No pending requests for #{job_name}."
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
job = @db.fetch_job(job_name, job_id)
|
19
|
+
run_job(job)
|
20
|
+
|
21
|
+
job.respond_to?(:output) ? job.output : {}
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def run_job(job)
|
27
|
+
job.fields['RanBy'] = @db.user.user_name
|
28
|
+
job.fields['StartedAt'] = Time.now.to_s
|
29
|
+
@db.update_job_status(job, :running)
|
30
|
+
|
31
|
+
failed = false
|
32
|
+
log = capture_stdout do
|
33
|
+
puts "#{job.fields['JobId']} started at #{job.fields['StartedAt']}."
|
12
34
|
begin
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rescue Exception
|
35
|
+
job.run
|
36
|
+
rescue
|
37
|
+
failed = true
|
17
38
|
puts $!, $@
|
18
|
-
|
39
|
+
end
|
40
|
+
job.fields['FinishedAt'] = Time.now.to_s
|
41
|
+
puts "#{job.fields['JobId']} finished at #{job.fields['FinishedAt']}."
|
42
|
+
|
43
|
+
if job.respond_to? :output
|
44
|
+
data = @db.insert_job_data(job.output)
|
45
|
+
job.fields['OutputDataId'] = data['JobDataId']
|
19
46
|
end
|
20
47
|
end
|
48
|
+
job.fields['Log'] = log.split("\n")
|
21
49
|
|
22
|
-
|
50
|
+
if failed
|
51
|
+
@db.update_job_status(job, :failed)
|
52
|
+
else
|
53
|
+
@db.update_job_status(job, :completed)
|
54
|
+
end
|
23
55
|
end
|
24
56
|
|
25
57
|
private
|
26
58
|
|
27
|
-
def
|
59
|
+
def capture_stdout
|
28
60
|
begin
|
29
61
|
old_stdout = $stdout
|
30
62
|
$stdout = StringIO.new('', 'w')
|
@@ -35,10 +67,5 @@ module Kindly
|
|
35
67
|
end
|
36
68
|
end
|
37
69
|
|
38
|
-
def write_log_file(migration, output)
|
39
|
-
filename = "#{migration.filename}.log"
|
40
|
-
File.open(filename, 'w') { |file| file.write(output) }
|
41
|
-
end
|
42
|
-
|
43
70
|
end
|
44
71
|
end
|
data/lib/kindly/version.rb
CHANGED
data/lib/kindly.rb
CHANGED
@@ -1,38 +1,23 @@
|
|
1
|
-
require 'kindly/
|
1
|
+
require 'kindly/db'
|
2
|
+
require 'kindly/queue'
|
3
|
+
require 'kindly/registry'
|
4
|
+
require 'kindly/requester'
|
2
5
|
require 'kindly/runner'
|
3
|
-
require 'kindly/handlers'
|
4
|
-
require 'kindly/handlers/do_nothing'
|
5
6
|
require 'kindly/version'
|
7
|
+
require 'kindly/jobs/do_nothing'
|
8
|
+
require 'kindly/jobs/test_job'
|
9
|
+
require 'kindly/jobs/test_job_with_input'
|
10
|
+
require 'kindly/jobs/test_job_with_output'
|
11
|
+
require 'aws-sdk'
|
6
12
|
|
7
13
|
module Kindly
|
8
14
|
|
9
|
-
def self.run(
|
10
|
-
|
11
|
-
puts "Kindly run #{handler_name} in #{source} directory."
|
12
|
-
|
13
|
-
handler = Handlers.find(handler_name)
|
14
|
-
runner = Runner.new(handler)
|
15
|
-
migrations = find_migrations(handler.ext)
|
16
|
-
|
17
|
-
puts "No migrations found for #{handler_name} handler." if migrations.empty?
|
18
|
-
migrations.each { |migration| runner.run(migration) }
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.source
|
22
|
-
@@source
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def self.find_migrations(ext)
|
28
|
-
filenames = Dir[File.join(source, 'pending', "*.#{ext}")]
|
29
|
-
build_migrations(filenames)
|
15
|
+
def self.run(job_name)
|
16
|
+
Runner.new.run(job_name)
|
30
17
|
end
|
31
18
|
|
32
|
-
def self.
|
33
|
-
|
34
|
-
filenames.each { |filename| migrations << Migration.new(filename) }
|
35
|
-
migrations
|
19
|
+
def self.request(job_name, input = {})
|
20
|
+
Requester.new.request(job_name, input)
|
36
21
|
end
|
37
22
|
|
38
23
|
end
|
@@ -1,108 +1,114 @@
|
|
1
|
-
require 'kindly'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'mocha/mini_test'
|
4
|
-
|
5
|
-
describe '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
1
|
+
# require 'kindly'
|
2
|
+
# require 'minitest/autorun'
|
3
|
+
# require 'mocha/mini_test'
|
4
|
+
#
|
5
|
+
# describe 'Job' do
|
6
|
+
# let(:source) { File.join('test', 'fixtures', 'jobs', 'read_json') }
|
7
|
+
# let(:config) {
|
8
|
+
# {
|
9
|
+
# :source => source,
|
10
|
+
# :pending => File.join(source, 'pending'),
|
11
|
+
# :running => File.join(source, 'running'),
|
12
|
+
# :completed => File.join(source, 'completed'),
|
13
|
+
# :failed => File.join(source, 'failed')
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
# let(:filename) { File.join(config[:pending], 'one.json') }
|
17
|
+
# let(:job) { job = Kindly::Job.new(filename) }
|
18
|
+
#
|
19
|
+
# before(:each) do
|
20
|
+
# Kindly.stubs(:config).returns(config)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# describe 'logs' do
|
24
|
+
# before(:each) { job.stubs(:move) }
|
25
|
+
#
|
26
|
+
# it 'when running' do
|
27
|
+
# output = capture_output { job.running! }
|
28
|
+
# assert running?(output)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# it 'when completed' do
|
32
|
+
# output = capture_output { job.completed! }
|
33
|
+
# assert completed?(output)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# it 'when failed' do
|
37
|
+
# output = capture_output { job.failed! }
|
38
|
+
# assert failed?(output)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def running?(output)
|
42
|
+
# output.include?("#{filename} running")
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# def completed?(output)
|
46
|
+
# output.include?("#{filename} completed") && !failed?(output)
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# def failed?(output)
|
50
|
+
# output.include?("#{filename} failed") && !completed?(output)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# describe 'moves files' do
|
56
|
+
#
|
57
|
+
# let(:tmp_dir) { File.join(source, 'tmp') }
|
58
|
+
#
|
59
|
+
# before(:each) do
|
60
|
+
# FileUtils.mkdir(tmp_dir) unless Dir.exist?(tmp_dir)
|
61
|
+
# Dir[File.join(source, 'pending', '*.json')].each do |file|
|
62
|
+
# FileUtils.cp(file, tmp_dir)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# after(:each) do
|
67
|
+
# restore_pending
|
68
|
+
# remove(config[:running])
|
69
|
+
# remove(config[:completed])
|
70
|
+
# remove(config[:failed])
|
71
|
+
# remove(tmp_dir)
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# it 'when running' do
|
75
|
+
# capture_output { job.running! }
|
76
|
+
# assert File.exist?(File.join(config[:running], File.basename(filename)))
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# it 'when completed' do
|
80
|
+
# capture_output { job.completed! }
|
81
|
+
# assert File.exist?(File.join(config[:completed], File.basename(filename)))
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# it 'when failed' do
|
85
|
+
# capture_output { job.failed! }
|
86
|
+
# assert File.exist?(File.join(config[:failed], File.basename(filename)))
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# def restore_pending
|
90
|
+
# FileUtils.rm_r(config[:pending])
|
91
|
+
# FileUtils.mkdir(config[:pending])
|
92
|
+
# Dir[File.join(tmp_dir, '*.json')].each do |file|
|
93
|
+
# FileUtils.cp(file, config[:pending])
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# def remove(dir)
|
98
|
+
# FileUtils.rm_r(dir) if Dir.exist?(dir)
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# def capture_output
|
104
|
+
# begin
|
105
|
+
# old_stdout = $stdout
|
106
|
+
# $stdout = StringIO.new('', 'w')
|
107
|
+
# yield
|
108
|
+
# $stdout.string
|
109
|
+
# ensure
|
110
|
+
# $stdout = old_stdout
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'kindly'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
describe 'Kindly::Registry' do
|
5
|
+
|
6
|
+
let(:registry) { Kindly::Registry }
|
7
|
+
let(:do_nothing) { Kindly::Jobs::DoNothing.new }
|
8
|
+
|
9
|
+
it 'throws if job is not registered' do
|
10
|
+
assert_raises(RuntimeError) { registry.find(:missing) }
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'allows job to be registered' do
|
14
|
+
registry.register :do_nothing, do_nothing
|
15
|
+
assert registry.find(:do_nothing) == do_nothing
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows job to be unregistered' do
|
19
|
+
registry.register :do_nothing, do_nothing
|
20
|
+
registry.unregister :do_nothing
|
21
|
+
assert_raises(RuntimeError) { registry.find(:do_nothing) }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/test/kindly/runner_test.rb
CHANGED
@@ -1,44 +1,77 @@
|
|
1
1
|
require 'kindly'
|
2
2
|
require 'minitest/autorun'
|
3
3
|
require 'mocha/mini_test'
|
4
|
-
require '
|
4
|
+
require 'fixtures/jobs/fail'
|
5
5
|
|
6
|
-
describe 'Runner' do
|
7
6
|
|
8
|
-
|
9
|
-
let(:migration) { migration = Kindly::Migration.new(filename) }
|
10
|
-
let(:runner) { Kindly::Runner.new(Kindly::Handlers::DoNothing.new) }
|
7
|
+
describe 'Kindly::Runner' do
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
# let(:job) { mock() }
|
10
|
+
#
|
11
|
+
# before(:each) do
|
12
|
+
# job.stubs(:fetch)
|
13
|
+
# job.stubs(:data)
|
14
|
+
# job.stubs(:running!)
|
15
|
+
# end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
migration.stubs(:load).raises
|
28
|
-
migration.expects(:running!).once
|
29
|
-
runner.run(migration)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'sets migration to completed if Runner succeeds' do
|
33
|
-
migration.expects(:completed!).once
|
34
|
-
migration.expects(:failed!).never
|
35
|
-
runner.run(migration)
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'writes log file' do
|
39
|
-
runner.run(migration)
|
40
|
-
expected_log_file = "#{migration.filename}.log"
|
41
|
-
assert File.exist?(expected_log_file)
|
42
|
-
end
|
17
|
+
# it 'returns if the job is a success' do
|
18
|
+
# job.stubs(:completed!)
|
19
|
+
# result = Kindly::Runner.new(:do_nothing).run(job)
|
20
|
+
# assert result[:success]
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# it 'returns if the job is not a success' do
|
24
|
+
# job.stubs(:failed!)
|
25
|
+
# result = Kindly::Runner.new(:fail).run(job)
|
26
|
+
# refute result[:success]
|
27
|
+
# end
|
43
28
|
|
44
29
|
end
|
30
|
+
|
31
|
+
# require 'kindly'
|
32
|
+
# require 'minitest/autorun'
|
33
|
+
# require 'mocha/mini_test'
|
34
|
+
# require 'fileutils'
|
35
|
+
# require 'fixtures/handlers/fail'
|
36
|
+
#
|
37
|
+
# describe 'Runner' do
|
38
|
+
#
|
39
|
+
# let(:read_json) { File.join('test', 'fixtures', 'jobs', 'read_json') }
|
40
|
+
# let(:filename) { File.join(read_json, 'pending', 'one.json') }
|
41
|
+
# let(:job) { job = Kindly::Job.new(filename) }
|
42
|
+
# let(:runner) { Kindly::Runner.new(Kindly::Handlers::DoNothing.new) }
|
43
|
+
# let(:runner_that_fails) { Kindly::Runner.new(Fixtures::Handlers::Fail.new) }
|
44
|
+
#
|
45
|
+
# before(:each) do
|
46
|
+
# job.stubs(:move)
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# after(:each) do
|
50
|
+
# logfile = "#{filename}.log"
|
51
|
+
# FileUtils.rm(logfile) if File.exist?(logfile)
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# it 'sets job to running' do
|
55
|
+
# job.expects(:running!).once
|
56
|
+
# runner.run(job)
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# it 'sets job to completed if job succeeds' do
|
60
|
+
# job.expects(:completed!).once
|
61
|
+
# job.expects(:failed!).never
|
62
|
+
# runner.run(job)
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# it 'sets job to failed if job fails' do
|
66
|
+
# job.expects(:completed!).never
|
67
|
+
# job.expects(:failed!).once
|
68
|
+
# runner_that_fails.run(job)
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# it 'writes log file' do
|
72
|
+
# runner.run(job)
|
73
|
+
# expected_log_file = "#{job.filename}.log"
|
74
|
+
# assert File.exist?(expected_log_file)
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# end
|
data/test/kindly_test.rb
CHANGED
@@ -1,27 +1,60 @@
|
|
1
1
|
require 'kindly'
|
2
2
|
require 'minitest/autorun'
|
3
|
-
require 'mocha/mini_test'
|
3
|
+
# require 'mocha/mini_test'
|
4
4
|
|
5
5
|
describe 'Kindly' do
|
6
6
|
|
7
|
-
it '
|
8
|
-
|
9
|
-
|
10
|
-
capture_output { Kindly.run(:do_nothing) }
|
11
|
-
end
|
7
|
+
# it 'works' do
|
8
|
+
# Kindly.request :test_job
|
9
|
+
# end
|
12
10
|
|
13
|
-
it '
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
# it 'returns true if message was handled' do
|
12
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
13
|
+
# Kindly::Queue.any_instance.stubs(:pop).returns([1, { hello: 'world' }])
|
14
|
+
# Kindly::Queue.any_instance.stubs(:delete)
|
15
|
+
# assert Kindly.run(:do_nothing)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# it 'returns false if message was not found' do
|
19
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
20
|
+
# Kindly::Queue.any_instance.stubs(:pop).returns([nil, nil])
|
21
|
+
# Kindly::Queue.any_instance.stubs(:delete)
|
22
|
+
# capture_output { refute Kindly.run(:do_nothing) }
|
23
|
+
# end
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
# let(:read_json) { File.join('test', 'fixtures', 'jobs', 'read_json') }
|
26
|
+
# let(:pending) { File.join(read_json, 'pending') }
|
27
|
+
#
|
28
|
+
# it 'runs given handler name' do
|
29
|
+
# Kindly.stubs(:config).returns(:source => read_json)
|
30
|
+
# Kindly::Runner.any_instance.expects(:run).twice
|
31
|
+
# capture_output { Kindly.run(:do_nothing) }
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# it 'defaults source to _jobs' do
|
35
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
36
|
+
# capture_output { Kindly.run(:do_nothing) }
|
37
|
+
# assert Kindly.config[:source] == '_jobs'
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# it 'allows source to be overridden' do
|
41
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
42
|
+
# capture_output { Kindly.run(:do_nothing, :source => read_json) }
|
43
|
+
# assert Kindly.config[:source] == read_json
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# it 'defaults pending to _jobs/pending' do
|
47
|
+
# expected = File.join('_jobs', 'pending')
|
48
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
49
|
+
# capture_output { Kindly.run(:do_nothing) }
|
50
|
+
# assert Kindly.config[:pending] == expected
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# it 'allows pending to be overridden' do
|
54
|
+
# Kindly::Runner.any_instance.stubs(:run)
|
55
|
+
# capture_output { Kindly.run(:do_nothing, :pending => pending) }
|
56
|
+
# assert_equal pending, Kindly.config[:pending]
|
57
|
+
# end
|
25
58
|
|
26
59
|
def capture_output
|
27
60
|
begin
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kindly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Scott
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mercenary
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,11 +80,24 @@ dependencies:
|
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '1.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.8'
|
69
97
|
description:
|
70
98
|
email:
|
71
99
|
- i@gregoryjscott.com
|
72
|
-
executables:
|
73
|
-
- kindly
|
100
|
+
executables: []
|
74
101
|
extensions: []
|
75
102
|
extra_rdoc_files: []
|
76
103
|
files:
|
@@ -79,18 +106,21 @@ files:
|
|
79
106
|
- LICENSE
|
80
107
|
- README.md
|
81
108
|
- Rakefile
|
82
|
-
- bin/kindly
|
83
109
|
- kindly.gemspec
|
84
110
|
- lib/kindly.rb
|
85
|
-
- lib/kindly/
|
86
|
-
- lib/kindly/
|
87
|
-
- lib/kindly/
|
111
|
+
- lib/kindly/db.rb
|
112
|
+
- lib/kindly/jobs/do_nothing.rb
|
113
|
+
- lib/kindly/jobs/test_job.rb
|
114
|
+
- lib/kindly/jobs/test_job_with_input.rb
|
115
|
+
- lib/kindly/jobs/test_job_with_output.rb
|
116
|
+
- lib/kindly/queue.rb
|
117
|
+
- lib/kindly/registry.rb
|
118
|
+
- lib/kindly/requester.rb
|
88
119
|
- lib/kindly/runner.rb
|
89
120
|
- lib/kindly/version.rb
|
90
|
-
- test/fixtures/
|
91
|
-
- test/fixtures/pending/two.json
|
92
|
-
- test/kindly/handlers_test.rb
|
121
|
+
- test/fixtures/jobs/fail.rb
|
93
122
|
- test/kindly/migration_test.rb
|
123
|
+
- test/kindly/registry_test.rb
|
94
124
|
- test/kindly/runner_test.rb
|
95
125
|
- test/kindly_test.rb
|
96
126
|
homepage: https://github.com/gregoryjscott/kindly
|
@@ -113,14 +143,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
143
|
version: '0'
|
114
144
|
requirements: []
|
115
145
|
rubyforge_project:
|
116
|
-
rubygems_version: 2.
|
146
|
+
rubygems_version: 2.4.5
|
117
147
|
signing_key:
|
118
148
|
specification_version: 4
|
119
|
-
summary: Kindly run
|
149
|
+
summary: Kindly run jobs of any kind.
|
120
150
|
test_files:
|
121
|
-
- test/fixtures/
|
122
|
-
- test/fixtures/pending/two.json
|
123
|
-
- test/kindly/handlers_test.rb
|
151
|
+
- test/fixtures/jobs/fail.rb
|
124
152
|
- test/kindly/migration_test.rb
|
153
|
+
- test/kindly/registry_test.rb
|
125
154
|
- test/kindly/runner_test.rb
|
126
155
|
- test/kindly_test.rb
|
data/bin/kindly
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
$:.unshift File.join(File.dirname(__FILE__), *%w{ .. lib })
|
4
|
-
|
5
|
-
require 'kindly'
|
6
|
-
require 'mercenary'
|
7
|
-
|
8
|
-
Mercenary.program(:kindly) do |p|
|
9
|
-
p.version Kindly::VERSION
|
10
|
-
p.description 'Kindly run migrations of any kind.'
|
11
|
-
p.syntax 'kindly <handler> [options]'
|
12
|
-
|
13
|
-
p.option 'source', '--source DIR', 'Host directory of the kindly directory structure.'
|
14
|
-
p.action do |args, options|
|
15
|
-
if args.empty?
|
16
|
-
puts p
|
17
|
-
elsif options['source']
|
18
|
-
Kindly.run(args.first.to_sym, options['source'])
|
19
|
-
else
|
20
|
-
Kindly.run(args.first.to_sym)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'kindly'
|
2
|
-
|
3
|
-
module Kindly
|
4
|
-
module Handlers
|
5
|
-
class DoNothing
|
6
|
-
|
7
|
-
def ext
|
8
|
-
'*'
|
9
|
-
end
|
10
|
-
|
11
|
-
def run(migration)
|
12
|
-
puts "The handler for #{migration.filename} did nothing."
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
register(:do_nothing, DoNothing.new)
|
18
|
-
end
|
19
|
-
end
|
data/lib/kindly/handlers.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'kindly'
|
2
|
-
|
3
|
-
module Kindly
|
4
|
-
module Handlers
|
5
|
-
|
6
|
-
def self.register(name, handler)
|
7
|
-
@@handlers ||= {}
|
8
|
-
@@handlers[name.to_sym] = handler
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.unregister(name)
|
12
|
-
@@handlers.delete(name.to_sym)
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.find(name)
|
16
|
-
if not_registered(name)
|
17
|
-
raise "No handler registered with name #{name.to_sym}."
|
18
|
-
end
|
19
|
-
|
20
|
-
@@handlers[name.to_sym]
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def self.not_registered(name)
|
26
|
-
!@@handlers.has_key?(name.to_sym)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/kindly/migration.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'kindly'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Kindly
|
6
|
-
class Migration
|
7
|
-
|
8
|
-
attr_reader :filename
|
9
|
-
|
10
|
-
def initialize(filename)
|
11
|
-
@filename = filename
|
12
|
-
end
|
13
|
-
|
14
|
-
def move(destination)
|
15
|
-
path = File.join(Kindly.source, destination)
|
16
|
-
FileUtils.mkdir(path) unless Dir.exist?(path)
|
17
|
-
FileUtils.mv(@filename, path)
|
18
|
-
@filename = File.join(path, File.basename(@filename))
|
19
|
-
end
|
20
|
-
|
21
|
-
def running!
|
22
|
-
move('running')
|
23
|
-
puts "#{@filename} running."
|
24
|
-
end
|
25
|
-
|
26
|
-
def completed!
|
27
|
-
move('completed')
|
28
|
-
puts "#{@filename} completed."
|
29
|
-
end
|
30
|
-
|
31
|
-
def failed!
|
32
|
-
move('failed')
|
33
|
-
puts "#{@filename} failed."
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'kindly'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
|
4
|
-
describe 'Kindly::Handlers' do
|
5
|
-
|
6
|
-
let(:handlers) { Kindly::Handlers }
|
7
|
-
let(:handler) { Kindly::Handlers::DoNothing.new }
|
8
|
-
|
9
|
-
it 'throws if handler is not registered' do
|
10
|
-
assert_raises(RuntimeError) { handlers.find(:missing) }
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'allows handlers to be registered' do
|
14
|
-
handlers.register :different, handler
|
15
|
-
assert handlers.find(:different) == handler
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'allows handlers to be unregistered' do
|
19
|
-
handlers.register :different, handler
|
20
|
-
handlers.unregister :different
|
21
|
-
assert_raises(RuntimeError) { handlers.find(:different) }
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|