robot_sweatshop 0.3.3 → 0.4.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/README.md +11 -11
- data/Rakefile +1 -1
- data/bin/README.md +23 -0
- data/bin/sweatshop +1 -1
- data/bin/sweatshop-assembler +101 -0
- data/bin/sweatshop-conveyor +64 -0
- data/bin/{sweatshop-input-http → sweatshop-input} +13 -7
- data/bin/sweatshop-job-dictionary +53 -0
- data/bin/sweatshop-payload-parser +28 -28
- data/bin/sweatshop-worker +58 -0
- data/config.defaults.yaml +6 -4
- data/lib/README.md +10 -13
- data/lib/robot_sweatshop/cli/common.rb +1 -1
- data/lib/robot_sweatshop/cli/config.rb +1 -1
- data/lib/robot_sweatshop/cli/job.rb +1 -1
- data/lib/robot_sweatshop/cli/start.rb +2 -2
- data/lib/robot_sweatshop/config.rb +2 -0
- data/lib/robot_sweatshop/connections.rb +17 -0
- data/lib/robot_sweatshop/create-config-directories.rb +0 -1
- data/lib/robot_sweatshop.rb +1 -2
- data/robot_sweatshop.eye +30 -32
- data/robot_sweatshop.gemspec +8 -6
- data/test/README.md +3 -1
- data/test/all.rb +35 -0
- data/test/assembler_spec.rb +89 -0
- data/test/conveyor_spec.rb +67 -0
- data/test/data/weird_job.yaml +6 -0
- data/test/end-to-end_spec.rb +19 -13
- data/test/input_spec.rb +49 -0
- data/test/job_dictionary_spec.rb +70 -0
- data/test/payload_parser_spec.rb +37 -26
- data/test/shared/helpers/input.rb +73 -0
- data/test/shared/helpers/output.rb +14 -0
- data/test/shared/helpers.rb +4 -83
- data/test/shared/scaffolding.rb +42 -0
- data/test/shared/stub.rb +44 -0
- data/test/worker_spec.rb +59 -0
- metadata +76 -44
- data/bin/sweatshop-job-assembler +0 -82
- data/bin/sweatshop-job-worker +0 -45
- data/bin/sweatshop-queue-broadcaster +0 -20
- data/bin/sweatshop-queue-handler +0 -24
- data/bin/sweatshop-queue-watcher +0 -18
- data/lib/robot_sweatshop/moneta-queue.rb +0 -49
- data/lib/robot_sweatshop/queue-helper.rb +0 -32
- data/robot_sweatshop.production.eye +0 -7
- data/robot_sweatshop.testing.eye +0 -7
- data/test/input_http_spec.rb +0 -49
- data/test/job_assembler_spec.rb +0 -80
- data/test/job_worker_spec.rb +0 -65
- data/test/moneta-queue_spec.rb +0 -49
- data/test/queue_broadcaster_spec.rb +0 -39
- data/test/queue_handler_spec.rb +0 -54
- data/test/run_all.rb +0 -3
- data/test/shared/process_spawning.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0549401a67fbd6e2dc3b5b22017d42700958f9c9
|
4
|
+
data.tar.gz: 5f741330b8b0999cb078cf0508e9440f595e02e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1c7523aea2b3006952303d6e3dfb9f13886cd304479ecec7c5841cfaba5c1455aed8a6ff13815b18ae7ec6af7904dee785333d30ad1a4a4af52badebef6ca5a
|
7
|
+
data.tar.gz: 83b86d2de23a74dc9a4623fb23417e37d822271b42e96b05cb3c7cc6f62eb764924d11beb8b1b0417199ac5cdefaa2b09918f670f930b84d24c20558a09993ac
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -10,20 +10,20 @@ Robot Sweatshop is a single-purpose CI server that runs collections of arbitrary
|
|
10
10
|
|
11
11
|
- install [ZMQ as described in the EZMQ gem](https://github.com/colstrom/ezmq#operating-system-notes)
|
12
12
|
- `gem install robot_sweatshop`
|
13
|
-
- `sweatshop start`
|
13
|
+
- `sweatshop start`
|
14
14
|
- `sweatshop job example --auto`
|
15
|
-
- `curl -
|
15
|
+
- `curl -d '' localhost:8080/run/example`
|
16
16
|
- `cat .robot_sweatshop/log/job-worker.log`
|
17
17
|
|
18
18
|
# Usage
|
19
19
|
|
20
|
-
Drop the `--auto` flag to create the job interactively. You can specify which branches will trigger the job, which commands will be run, and any environment variables you might need.
|
20
|
+
Drop the `--auto` flag to create the job interactively. You can specify which branches will trigger the job, which commands will be run, and any environment variables you might need. See [the wiki](https://github.com/JScott/robot_sweatshop/wiki/Job-configuration) for more details.
|
21
21
|
|
22
22
|
Robot Sweatshop uses [Eye](https://github.com/kostya/eye) to handle its processes so you can use its commandline tool to monitor their status.
|
23
23
|
|
24
24
|
# Configuration
|
25
25
|
|
26
|
-
By default, Robot Sweatshop looks in your current working path to configure and run. You can supply a custom configuration with `sweatshop config [local|user|system]`.
|
26
|
+
By default, Robot Sweatshop looks in your current working path to configure and run. You can supply a custom configuration with `sweatshop config [local|user|system]`. See [the wiki](https://github.com/JScott/robot_sweatshop/wiki) for more information.
|
27
27
|
|
28
28
|
# Supported payload formats
|
29
29
|
|
@@ -34,21 +34,21 @@ By default, Robot Sweatshop looks in your current working path to configure and
|
|
34
34
|
|
35
35
|
# Security
|
36
36
|
|
37
|
-
You probably don't want to run Robot Sweatshop as a sudo user. Create a testing user and group and
|
37
|
+
You probably don't want to run Robot Sweatshop as a sudo user. Create a testing user and group and run `sweatshop start` as them.
|
38
38
|
|
39
39
|
# Roadmap
|
40
40
|
|
41
41
|
1.0
|
42
42
|
|
43
|
-
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
- Support for multiple workers
|
43
|
+
- Docs on architecture
|
44
|
+
- Easier way to run multiple workers
|
45
|
+
- Mascot
|
48
46
|
|
49
47
|
Beyond
|
50
48
|
|
49
|
+
- Jenkins-to-Sweatshop job converting script
|
50
|
+
- Take a look at beefcake for data versioning/serialization
|
51
51
|
- Better logging for the processes
|
52
52
|
- CLI configuration via chomp and/or flags
|
53
|
-
- Common scrips such as git repo syncing and creating a job run ID
|
53
|
+
- Common scrips such as git repo syncing and creating a job run ID (see: [sweatshop-tears](https://github.com/JScott/sweatshop-tears))
|
54
54
|
- Use [eye-http](https://github.com/kostya/eye-http) for the '/' route?
|
data/Rakefile
CHANGED
data/bin/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Services
|
2
|
+
|
3
|
+
sweatshop-conveyor
|
4
|
+
```
|
5
|
+
{ method:, data: }
|
6
|
+
|
7
|
+
id = enqueue(item)
|
8
|
+
id = dequeue
|
9
|
+
item = lookup(id)
|
10
|
+
something = finish(id)
|
11
|
+
```
|
12
|
+
|
13
|
+
sweatshop-payload-parser
|
14
|
+
```
|
15
|
+
req: { payload:, user_agent: }
|
16
|
+
rep: { data:, error: }
|
17
|
+
```
|
18
|
+
|
19
|
+
sweatshop-job-dictionary
|
20
|
+
```
|
21
|
+
req: { job_name: }
|
22
|
+
rep: { data:, error: }
|
23
|
+
```
|
data/bin/sweatshop
CHANGED
@@ -8,7 +8,7 @@ require 'robot_sweatshop/config'
|
|
8
8
|
require 'robot_sweatshop/create-config-directories'
|
9
9
|
|
10
10
|
program :name, 'Robot Sweatshop'
|
11
|
-
program :version, '0.
|
11
|
+
program :version, '0.4.0'
|
12
12
|
program :description, 'A lightweight, unopinionated CI server'
|
13
13
|
program :help, 'Author', 'Justin Scott <jvscott@gmail.com>'
|
14
14
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'yaml'
|
4
|
+
require 'contracts'
|
5
|
+
require 'exponential_backoff'
|
6
|
+
require 'ezmq'
|
7
|
+
require 'robot_sweatshop/config'
|
8
|
+
require 'robot_sweatshop/connections'
|
9
|
+
$stdout.sync = true
|
10
|
+
include Contracts
|
11
|
+
using ExtendedEZMQ
|
12
|
+
|
13
|
+
Contract Hash => Hash
|
14
|
+
def sanitize(data)
|
15
|
+
data = data.map { |key, value| {key => value.to_s} }
|
16
|
+
data.reduce(:merge)
|
17
|
+
end
|
18
|
+
|
19
|
+
Contract Hash, Hash => Hash
|
20
|
+
def job_context(job_environment, context_from_payload)
|
21
|
+
sanitize job_environment.merge(context_from_payload)
|
22
|
+
end
|
23
|
+
|
24
|
+
Contract Hash, Hash, Hash => Hash
|
25
|
+
def assemble(job, payload, definition)
|
26
|
+
{
|
27
|
+
commands: definition['commands'],
|
28
|
+
context: job_context(definition['environment'] || {}, payload),
|
29
|
+
job_name: job[:job_name],
|
30
|
+
job_id: job[:job_id]
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
Contract None => Or[Hash,nil]
|
35
|
+
def request_job
|
36
|
+
job_id = @sockets[:conveyor].request({method: 'dequeue'}, {})
|
37
|
+
return nil if job_id.nil?
|
38
|
+
raw_job = @sockets[:conveyor].request({method: 'lookup', data: job_id}, {})
|
39
|
+
puts "Assembling: '#{raw_job}'"
|
40
|
+
raw_job.merge job_id: job_id
|
41
|
+
end
|
42
|
+
|
43
|
+
Contract EZMQ::Socket, Any => Hash
|
44
|
+
def request(socket, data)
|
45
|
+
response = socket.request data, {}
|
46
|
+
raise response[:error] unless response[:error].empty?
|
47
|
+
response[:data]
|
48
|
+
end
|
49
|
+
|
50
|
+
Contract Hash => [Hash, Hash]
|
51
|
+
def request_data_for(job)
|
52
|
+
payload = request @sockets[:parser], job
|
53
|
+
definition = request @sockets[:dictionary], job[:job_name]
|
54
|
+
[payload, definition]
|
55
|
+
end
|
56
|
+
|
57
|
+
Contract Or[Array,nil], Or[String, nil] => nil
|
58
|
+
def check_whitelist(whitelist, branch)
|
59
|
+
return if whitelist.nil?
|
60
|
+
raise 'Branch not whitelisted' unless whitelist.include? branch
|
61
|
+
end
|
62
|
+
|
63
|
+
Contract Fixnum => Bool
|
64
|
+
def finish(job_id)
|
65
|
+
@sockets[:conveyor].request({method: 'finish', data: job_id}, {})
|
66
|
+
end
|
67
|
+
|
68
|
+
job_search = Fiber.new do
|
69
|
+
# TODO: profiler to get a better idea of how long we should wait based on historical information
|
70
|
+
timer = ExponentialBackoff.new 0.1, 3
|
71
|
+
loop do
|
72
|
+
sleep timer.next_interval
|
73
|
+
job = request_job
|
74
|
+
next if job.nil?
|
75
|
+
timer.clear
|
76
|
+
Fiber.yield job
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
puts 'Started'
|
81
|
+
@sockets = {
|
82
|
+
conveyor: EZMQ::Client.new(port: configatron.conveyor_port),
|
83
|
+
worker: EZMQ::Pusher.new(:bind, port: configatron.worker_port),
|
84
|
+
parser: EZMQ::Client.new(port: configatron.payload_parser_port),
|
85
|
+
dictionary: EZMQ::Client.new(port: configatron.job_dictionary_port)
|
86
|
+
}
|
87
|
+
@sockets.each { |_key, socket| socket.serialize_with_json! }
|
88
|
+
|
89
|
+
loop do
|
90
|
+
job = job_search.resume
|
91
|
+
begin
|
92
|
+
payload, definition = request_data_for job
|
93
|
+
check_whitelist definition['branch_whitelist'], payload['branch']
|
94
|
+
assembled_job = assemble job, payload, definition
|
95
|
+
@sockets[:worker].send(assembled_job, {})
|
96
|
+
rescue RuntimeError => error
|
97
|
+
puts error.message
|
98
|
+
finish job[:job_id]
|
99
|
+
next
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'ezmq'
|
4
|
+
require 'stubborn_queue'
|
5
|
+
require 'oj'
|
6
|
+
require 'contracts'
|
7
|
+
require 'robot_sweatshop/config'
|
8
|
+
require 'robot_sweatshop/connections'
|
9
|
+
using ExtendedEZMQ
|
10
|
+
|
11
|
+
queue_settings = {
|
12
|
+
name: 'test',
|
13
|
+
timeout: configatron.job_timeout_length,
|
14
|
+
file: "#{configatron.database_path}/conveyor.db"
|
15
|
+
}
|
16
|
+
@items = StubbornQueue.new queue_settings
|
17
|
+
$stdout.sync = true
|
18
|
+
include Contracts
|
19
|
+
|
20
|
+
Contract Hash => Fixnum
|
21
|
+
def enqueue(item)
|
22
|
+
puts "enqueue #{item}"
|
23
|
+
@items.enqueue Oj.dump(item)
|
24
|
+
end
|
25
|
+
|
26
|
+
Contract None => Or[Fixnum,nil]
|
27
|
+
def dequeue
|
28
|
+
puts "dequeue"
|
29
|
+
@items.dequeue
|
30
|
+
end
|
31
|
+
|
32
|
+
Contract Fixnum => Hash
|
33
|
+
def lookup(id)
|
34
|
+
puts "lookup #{id}"
|
35
|
+
Oj.load @items.lookup(id)
|
36
|
+
end
|
37
|
+
|
38
|
+
Contract Fixnum => Bool
|
39
|
+
def finish(id)
|
40
|
+
puts "finish #{id}"
|
41
|
+
@items.finish id
|
42
|
+
end
|
43
|
+
|
44
|
+
Contract String => Bool
|
45
|
+
def supported?(method)
|
46
|
+
%w(enqueue dequeue lookup finish).include? method
|
47
|
+
end
|
48
|
+
|
49
|
+
Contract Hash => Any
|
50
|
+
def complete(request)
|
51
|
+
arguments = []
|
52
|
+
return send(request[:method], request[:data]) if request[:data]
|
53
|
+
send(request[:method])
|
54
|
+
end
|
55
|
+
|
56
|
+
puts 'Starting the Conveyor'
|
57
|
+
server = EZMQ::Server.new port: configatron.conveyor_port
|
58
|
+
server.serialize_with_json!
|
59
|
+
server.listen do |request|
|
60
|
+
puts "Received: #{request.inspect}"
|
61
|
+
next unless request.is_a? Hash
|
62
|
+
next unless supported? request[:method]
|
63
|
+
complete request
|
64
|
+
end
|
@@ -2,28 +2,34 @@
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'sinatra'
|
4
4
|
require 'ezmq'
|
5
|
-
require 'json'
|
6
5
|
require 'robot_sweatshop/config'
|
6
|
+
require 'robot_sweatshop/connections'
|
7
|
+
using ExtendedEZMQ
|
7
8
|
|
8
9
|
configure do
|
9
10
|
set :port, configatron.http_port
|
10
11
|
set :bind, configatron.http_bind
|
11
12
|
set :run, true
|
13
|
+
set :conveyor, EZMQ::Client.new(port: configatron.conveyor_port)
|
14
|
+
settings.conveyor.serialize_with_json!
|
12
15
|
end
|
13
16
|
|
14
17
|
get '/' do
|
15
18
|
'Everything\'s on schedule!'
|
16
19
|
end
|
17
20
|
|
18
|
-
post '/
|
21
|
+
post '/run/:job_name' do
|
19
22
|
puts "Received payload for #{params['job_name']}"
|
20
23
|
request.body.rewind
|
21
24
|
payload = request.body.read
|
22
25
|
hash = {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
method: 'enqueue',
|
27
|
+
data: {
|
28
|
+
payload: payload,
|
29
|
+
user_agent: request.env['HTTP_USER_AGENT'],
|
30
|
+
job_name: params['job_name']
|
31
|
+
}
|
26
32
|
}
|
27
|
-
|
28
|
-
|
33
|
+
settings.conveyor.request hash, {}
|
34
|
+
200
|
29
35
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'oj'
|
4
|
+
require 'ezmq'
|
5
|
+
require 'contracts'
|
6
|
+
require 'robot_sweatshop/config'
|
7
|
+
require 'robot_sweatshop/connections'
|
8
|
+
$stdout.sync = true
|
9
|
+
include Contracts
|
10
|
+
using ExtendedEZMQ
|
11
|
+
|
12
|
+
Contract None => String
|
13
|
+
def job_path
|
14
|
+
File.expand_path configatron.job_path
|
15
|
+
end
|
16
|
+
|
17
|
+
Contract String => Or[Hash, nil]
|
18
|
+
def load_if_exists(config_path)
|
19
|
+
puts "Reading job configuration from #{config_path}"
|
20
|
+
YAML.load_file config_path if File.exists? config_path
|
21
|
+
end
|
22
|
+
|
23
|
+
Contract None => Hash
|
24
|
+
def empty_config
|
25
|
+
puts "Job configuration not found or empty"
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
|
29
|
+
Contract String => Hash
|
30
|
+
def load_config_for(job_name)
|
31
|
+
load_if_exists("#{job_path}/#{job_name}.yaml") || empty_config
|
32
|
+
end
|
33
|
+
|
34
|
+
Contract Hash, Hash => Hash
|
35
|
+
def formatted(payload={}, error:'')
|
36
|
+
{data: payload, error: error}
|
37
|
+
end
|
38
|
+
|
39
|
+
Contract String => Hash
|
40
|
+
def define(job_name)
|
41
|
+
config = load_config_for job_name
|
42
|
+
return formatted error: 'Job not found or empty' if config.empty?
|
43
|
+
return formatted error: 'No commands' unless config['commands'].is_a? Array
|
44
|
+
formatted config
|
45
|
+
end
|
46
|
+
|
47
|
+
puts 'Started'
|
48
|
+
server = EZMQ::Server.new port: configatron.job_dictionary_port
|
49
|
+
server.serialize_with_json!
|
50
|
+
server.listen do |job_name|
|
51
|
+
puts "Looking up: #{job_name}"
|
52
|
+
define job_name
|
53
|
+
end
|
@@ -1,20 +1,21 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'bundler/setup'
|
3
|
-
require '
|
3
|
+
require 'oj'
|
4
4
|
require 'ezmq'
|
5
5
|
require 'contracts'
|
6
6
|
require 'robot_sweatshop/config'
|
7
7
|
require 'robot_sweatshop/payload'
|
8
|
-
|
8
|
+
require 'robot_sweatshop/connections'
|
9
9
|
$stdout.sync = true
|
10
10
|
include Contracts
|
11
|
+
using ExtendedEZMQ
|
11
12
|
|
12
13
|
Contract String => Bool
|
13
14
|
def json?(string)
|
14
15
|
begin
|
15
|
-
|
16
|
+
Oj.load string
|
16
17
|
true
|
17
|
-
rescue
|
18
|
+
rescue Oj::ParseError => e
|
18
19
|
false
|
19
20
|
end
|
20
21
|
end
|
@@ -31,42 +32,41 @@ end
|
|
31
32
|
|
32
33
|
Contract Hash => String
|
33
34
|
def detect_format_of(request)
|
34
|
-
return 'empty' if request[
|
35
|
-
return 'Github' if from_github? request[
|
36
|
-
return 'Bitbucket' if from_bitbucket? request[
|
37
|
-
return 'JSON' if json? request[
|
35
|
+
return 'empty' if request[:payload].empty?
|
36
|
+
return 'Github' if from_github? request[:user_agent]
|
37
|
+
return 'Bitbucket' if from_bitbucket? request[:user_agent]
|
38
|
+
return 'JSON' if json? request[:payload]
|
38
39
|
'unsupported'
|
39
40
|
end
|
40
41
|
|
41
42
|
Contract String, String => Or[Hash, nil]
|
42
43
|
def payload_hash_from(payload, format)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
begin
|
45
|
+
Object.const_get("#{format}Payload").new(payload).to_hash
|
46
|
+
rescue NameError
|
47
|
+
nil
|
48
|
+
end
|
48
49
|
end
|
49
50
|
|
50
|
-
Contract Hash => Hash
|
51
|
-
def
|
52
|
-
|
53
|
-
return {payload: {}, error: ''} if format == 'empty'
|
54
|
-
payload = payload_hash_from request['payload'], format
|
55
|
-
return {payload: {}, error: "Can't parse #{format} payload"} if payload.nil?
|
56
|
-
{payload: payload, error: ''}
|
51
|
+
Contract Hash, Hash => Hash
|
52
|
+
def formatted(payload={}, error:'')
|
53
|
+
{data: payload, error: error}
|
57
54
|
end
|
58
55
|
|
59
|
-
Contract
|
60
|
-
def
|
61
|
-
return
|
62
|
-
|
56
|
+
Contract String, String => Hash
|
57
|
+
def parse(raw_payload, format)
|
58
|
+
return formatted if format == 'empty'
|
59
|
+
return formatted error: 'Unknown format' if format == 'unsupported'
|
60
|
+
payload = payload_hash_from raw_payload, format
|
61
|
+
return formatted error: "Invalid #{format} payload" if payload.nil?
|
62
|
+
formatted payload
|
63
63
|
end
|
64
64
|
|
65
65
|
puts 'Started'
|
66
|
-
server = EZMQ::Server.new port: configatron.payload_parser_port
|
66
|
+
server = EZMQ::Server.new port: configatron.payload_parser_port
|
67
|
+
server.serialize_with_json!
|
67
68
|
server.listen do |request|
|
68
69
|
puts "Parsing: #{request}"
|
69
|
-
|
70
|
-
|
71
|
-
JSON.dump request
|
70
|
+
format = detect_format_of request
|
71
|
+
parse request[:payload], format
|
72
72
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'ezmq'
|
4
|
+
require 'faker'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'contracts'
|
7
|
+
require 'robot_sweatshop/config'
|
8
|
+
require 'robot_sweatshop/connections'
|
9
|
+
using ExtendedEZMQ
|
10
|
+
$stdout.sync = true
|
11
|
+
$stderr.sync = true
|
12
|
+
include Contracts
|
13
|
+
|
14
|
+
# TODO: check existing worker ids. it'd be a problem to share a workspace
|
15
|
+
@worker_id = ARGV[0] || "#{Faker::Name.first_name}"
|
16
|
+
|
17
|
+
Contract Hash, Proc => Any
|
18
|
+
def from_workspace(named:)
|
19
|
+
workspace = "#{named}-#{@worker_id}"
|
20
|
+
puts "Workspace: #{workspace}"
|
21
|
+
path = File.expand_path "#{configatron.workspace_path}/#{workspace}"
|
22
|
+
FileUtils.mkpath path
|
23
|
+
Dir.chdir(path) { yield if block_given? }
|
24
|
+
end
|
25
|
+
|
26
|
+
Contract Hash, String => nil
|
27
|
+
def execute(context, command)
|
28
|
+
puts "Executing '#{command}'..."
|
29
|
+
# TODO: path.split(' ') to bypass the shell when we're not using env vars
|
30
|
+
IO.popen(context, command) do |io_stream|
|
31
|
+
while line = io_stream.gets
|
32
|
+
puts line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
puts "Execution complete with exit status: #{$?.exitstatus}"
|
36
|
+
end
|
37
|
+
|
38
|
+
Contract Fixnum => nil
|
39
|
+
def finish(id)
|
40
|
+
@sockets[:conveyor].request({method: 'finish', data: id}, {})
|
41
|
+
puts "Job finished.\n\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
puts 'Starting'
|
45
|
+
@sockets = {
|
46
|
+
conveyor: EZMQ::Client.new(port: configatron.conveyor_port),
|
47
|
+
puller: EZMQ::Puller.new(:connect, port: configatron.worker_port)
|
48
|
+
}
|
49
|
+
@sockets.each { |_key, socket| socket.serialize_with_json! }
|
50
|
+
|
51
|
+
@sockets[:puller].listen do |data|
|
52
|
+
puts "Running: #{data}"
|
53
|
+
from_workspace named: data[:job_name] do
|
54
|
+
context = data[:context] || {}
|
55
|
+
data[:commands].each { |command| execute context, command }
|
56
|
+
end
|
57
|
+
finish data[:job_id]
|
58
|
+
end
|
data/config.defaults.yaml
CHANGED
@@ -3,9 +3,11 @@ pidfile_path: .robot_sweatshop/run
|
|
3
3
|
logfile_path: .robot_sweatshop/log
|
4
4
|
job_path: .robot_sweatshop/jobs
|
5
5
|
workspace_path: .robot_sweatshop/workspaces
|
6
|
-
|
6
|
+
database_path: .robot_sweatshop/db
|
7
7
|
http_port: 8080
|
8
8
|
http_bind: 0.0.0.0
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
conveyor_port: 5555
|
10
|
+
payload_parser_port: 5556
|
11
|
+
job_dictionary_port: 5557
|
12
|
+
worker_port: 5558
|
13
|
+
job_timeout_length: 300 # 5 minutes
|
data/lib/README.md
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
Job lifecycle:
|
2
2
|
|
3
3
|
```
|
4
|
-
|
5
|
-
|
6
|
-
job-worker
|
7
|
-
```
|
8
|
-
|
9
|
-
All passing done via the moneta core in queue/*
|
4
|
+
input -> conveyor
|
5
|
+
{ payload:, user_agent:, job_name: }
|
10
6
|
|
11
|
-
|
7
|
+
assembler <-> conveyor
|
12
8
|
|
13
|
-
|
9
|
+
assembler <-> payload-parser
|
10
|
+
assembler <-> job-dictionary
|
11
|
+
{ payload:, user_agent: } <-> { payload:, error: }
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
```
|
18
|
-
{ payload:, user_agent: } -> payload-parser -> { payload:, error: }
|
13
|
+
assembler -> worker
|
14
|
+
{ context:, commands:, job_name:, job_id }
|
19
15
|
```
|
20
16
|
|
21
|
-
|
17
|
+
context is passed around with string keys because it's user provided
|
18
|
+
everything else is passed with symbol keys
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
|
2
|
+
require 'robot_sweatshop/config'
|
3
3
|
|
4
4
|
def store_config_for_eye
|
5
5
|
config = configatron.to_h
|
@@ -12,7 +12,7 @@ end
|
|
12
12
|
|
13
13
|
def start_sweatshop(for_environment:)
|
14
14
|
store_config_for_eye
|
15
|
-
eye_config = File.expand_path "#{__dir__}/../../../robot_sweatshop
|
15
|
+
eye_config = File.expand_path "#{__dir__}/../../../robot_sweatshop.eye"
|
16
16
|
output = `eye load #{eye_config}`
|
17
17
|
if $?.exitstatus != 0
|
18
18
|
notify :failure, output
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ezmq'
|
2
|
+
require 'oj'
|
3
|
+
require 'robot_sweatshop/config'
|
4
|
+
|
5
|
+
module ExtendedEZMQ
|
6
|
+
refine EZMQ::Socket do
|
7
|
+
def serialize_with_json!
|
8
|
+
self.encode = -> message { Oj.dump message }
|
9
|
+
self.decode = -> message { Oj.load message }
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
self.socket.close
|
14
|
+
self.context.terminate
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/robot_sweatshop.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require_relative 'robot_sweatshop/moneta-queue'
|
2
|
-
require_relative 'robot_sweatshop/queue-helper'
|
3
1
|
require_relative 'robot_sweatshop/config'
|
4
2
|
require_relative 'robot_sweatshop/payload'
|
5
3
|
require_relative 'robot_sweatshop/cli'
|
4
|
+
require_relative 'robot_sweatshop/connections'
|