robot_sweatshop 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 94213cf7a5098291271e0d6b1b053e822da20269
4
- data.tar.gz: c6e7f4877c894243ccb386f339bbef0ed4fc822d
3
+ metadata.gz: cbc73c13d57e32651f63077a89b03317769850f3
4
+ data.tar.gz: 834bb91075c655c1edc0ac7f4cc445c2048094db
5
5
  SHA512:
6
- metadata.gz: 42394c288d96179fd7dfc979e23eb44a5f9a75ea6bbdc43ea2c92c4180357df6c9b84ab0c09a0cf1632cbb59bcd1d3ac1092aea61450ce7e69867090f1fa33b9
7
- data.tar.gz: aa468e0ca4eafb867b68ee9c68c3ee423156394921c85a95429b20ad5499ba42a345e645169a563950ea17c3bb994e67b6124b2acf2847f15550272771240d72
6
+ metadata.gz: 16784f92f5abe86c20dd6aebe73d79d3d9d18296576ba50ed0018467b205038fb67517a18931a539b4309839f311427b079c5b3b0e4708f7120d7e0baf570365
7
+ data.tar.gz: bc6eca3c50fddb92091e7693b7f4aba3232ab96b857a3b495d7a20e557bff3f695cda3149842b718d5c5905011d04bdb4d6d9decaf3cca8a7dcd3c062b8a2e1e
data/README.md CHANGED
@@ -8,11 +8,11 @@ Robot Sweatshop is a single-purpose CI server that runs collections of arbitrary
8
8
 
9
9
  # Quick start
10
10
 
11
- - install [ZMQ as described in the EZMQ gem](https://github.com/colstrom/ezmq)
11
+ - install [ZMQ as described in the EZMQ gem](https://github.com/colstrom/ezmq#operating-system-notes)
12
12
  - `gem install robot_sweatshop`
13
13
  - `sweatshop start` ([you may need sudo on OSX](https://github.com/JScott/robot_sweatshop/wiki))
14
14
  - `sweatshop job example --auto`
15
- - POST a Github payload to `yourserver.com:8080/github/payload-for/example`
15
+ - `curl --data '{"your": "json"}' http://localhost:8080/payload-for/example`
16
16
  - `cat .robot_sweatshop/log/job-worker.log`
17
17
 
18
18
  # Usage
@@ -29,6 +29,7 @@ By default, Robot Sweatshop looks in your current working directory to configure
29
29
 
30
30
  - Github (application/json format only)
31
31
  - Bitbucket
32
+ - JSON
32
33
 
33
34
  # Security
34
35
 
@@ -36,12 +37,17 @@ You probably don't want to run Robot Sweatshop as a sudo user. Create a testing
36
37
 
37
38
  # Roadmap
38
39
 
39
- - CLI job running
40
- - custom/empty payloads
41
- - Common scrips such as git repo syncing and creating a job run ID
42
- - Support for multiple workers
43
- - Better logging for the processes
44
- - Use [eye-http](https://github.com/kostya/eye-http) for the '/' route?
40
+ 1.0
41
+
45
42
  - Improved architecture:
46
43
 
47
44
  ![Improved architecture diagram](http://40.media.tumblr.com/8a5b6ca59c0d93c4ce6fc6b733932a5f/tumblr_nko478zp9N1qh941oo1_1280.jpg)
45
+
46
+ - Support for multiple workers
47
+
48
+ Beyond
49
+
50
+ - Better logging for the processes
51
+ - CLI configuration via chomp and/or flags
52
+ - Common scrips such as git repo syncing and creating a job run ID
53
+ - Use [eye-http](https://github.com/kostya/eye-http) for the '/' route?
data/bin/sweatshop CHANGED
@@ -10,7 +10,7 @@ require_relative '../lib/sweatshop/config'
10
10
  require_relative '../lib/sweatshop/create-config-directories'
11
11
 
12
12
  program :name, 'Robot Sweatshop'
13
- program :version, '0.2.1'
13
+ program :version, '0.3.0'
14
14
  program :description, 'A lightweight, unopinionated CI server'
15
15
  program :help, 'Author', 'Justin Scott <jvscott@gmail.com>'
16
16
 
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'bundler/setup'
2
3
  require 'sinatra'
3
4
  require 'ezmq'
4
5
  require 'json'
5
- require_relative '../lib/sweatshop/config'
6
+ require 'sweatshop/config'
6
7
 
7
8
  configure do
8
9
  set :port, configatron.http_port
9
10
  set :bind, configatron.http_bind
10
- set :output_queue, 'raw-payload'
11
11
  set :run, true
12
12
  end
13
13
 
@@ -15,14 +15,15 @@ get '/' do
15
15
  'Everything\'s on schedule!'
16
16
  end
17
17
 
18
- post '/:format/payload-for/:job_name' do
19
- puts "Received #{params['format']} payload for #{params['job_name']}"
18
+ post '/payload-for/:job_name' do
19
+ puts "Received payload for #{params['job_name']}"
20
20
  request.body.rewind
21
+ payload = request.body.read
21
22
  hash = {
22
- payload: request.body.read,
23
- format: params['format'],
23
+ payload: payload,
24
+ user_agent: request.env['HTTP_USER_AGENT'],
24
25
  job_name: params['job_name']
25
26
  }
26
27
  client = EZMQ::Client.new port: 5556
27
- client.request "#{settings.output_queue} #{JSON.generate hash}"
28
+ client.request "payload #{JSON.generate hash}"
28
29
  end
@@ -1,45 +1,82 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'bundler/setup'
2
3
  require 'yaml'
3
- require_relative '../lib/sweatshop/queue-helper'
4
- require_relative '../lib/sweatshop/config'
5
-
6
- def load_config(for_job:)
7
- job_directory = File.expand_path configatron.job_directory
8
- file_path = "#{job_directory}/#{for_job}.yaml"
9
- puts file_path
10
- if File.file?(file_path)
11
- YAML.load_file file_path
12
- else
13
- puts "No config found for job '#{for_job}'"
14
- nil
15
- end
16
- end
17
-
18
- def serialize(value)
19
- value.is_a?(Hash) ? JSON.generate(value) : value.to_s
20
- end
21
-
22
- def assemble_job(data)
23
- job_config = load_config for_job: data['job_name']
24
- return nil if job_config.nil?
25
- if job_config['branch_whitelist'].include? data['payload']['branch']
26
- context = job_config['environment'].merge(data['payload'])
27
- context.each { |key, value| context[key] = serialize value }
28
- {
29
- commands: job_config['commands'],
30
- context: context,
31
- job_name: data['job_name']
32
- }
33
- else
34
- puts "Branch '#{data['payload']['branch']}' is not whitelisted"
35
- nil
36
- end
37
- end
38
-
39
- QueueHelper.wait_for('parsed-payload') do |data|
40
- puts "Assembling: #{data}"
41
- assembled_job = assemble_job data
4
+ require 'json'
5
+ require 'contracts'
6
+ require 'sweatshop/queue-helper'
7
+ require 'sweatshop/config'
8
+
9
+ $stdout.sync = true
10
+ include Contracts
11
+
12
+ Contract None => Hash
13
+ def empty_config
14
+ puts "Job configuration not found"
15
+ {}
16
+ end
17
+
18
+ Contract None => String
19
+ def job_directory
20
+ File.expand_path configatron.job_directory
21
+ end
22
+
23
+ Contract Or[String, nil], Hash => Bool
24
+ def whitelisted?(branch, in_config:)
25
+ return true if in_config['branch_whitelist'].nil?
26
+ in_config['branch_whitelist'].include? branch
27
+ end
28
+
29
+ Contract Hash => Hash
30
+ def sanitize(data)
31
+ data.each_pair { |key, value| data[key] = value.to_s }
32
+ end
33
+
34
+ Contract Hash, Hash => Hash
35
+ def job_context(job_environment, context_from_payload)
36
+ sanitize job_environment.merge(context_from_payload)
37
+ end
38
+
39
+ Contract String => Or[Hash, nil]
40
+ def load_if_exists(config)
41
+ puts "Reading job configuration from #{config}"
42
+ YAML.load_file config if File.exists? config
43
+ end
44
+
45
+ Contract String => Hash
46
+ def load_config_for(job_name)
47
+ load_if_exists("#{job_directory}/#{job_name}.yaml") || empty_config
48
+ end
49
+
50
+ Contract Hash => [Hash, String]
51
+ def parse_payload(request)
52
+ client = EZMQ::Client.new port: configatron.payload_parser_port
53
+ response = JSON.load client.request(JSON.dump request)
54
+ puts response['error'] unless response['error'].empty?
55
+ [response['payload'], response['error']]
56
+ end
57
+
58
+ Contract Hash, Hash => Bool
59
+ def can_work_with?(job_config, payload)
60
+ return false if job_config.empty?
61
+ return false unless whitelisted? payload['branch'], in_config: job_config
62
+ true
63
+ end
64
+
65
+ Contract Hash => Or[Hash, false]
66
+ def assemble_job(request)
67
+ job_config = load_config_for request['job_name']
68
+ payload, error = parse_payload request
69
+ return false unless error.empty? && can_work_with?(job_config, payload)
70
+ {
71
+ commands: job_config['commands'],
72
+ context: job_context(job_config['environment'], payload),
73
+ job_name: request['job_name']
74
+ }
75
+ end
76
+
77
+ puts 'Started'
78
+ QueueHelper.wait_for('payload') do |request|
79
+ puts "Assembling: #{request}"
80
+ assembled_job = assemble_job request
42
81
  QueueHelper.enqueue assembled_job, to: 'jobs' if assembled_job
43
- $stdout.flush
44
- $stderr.flush
45
82
  end
@@ -21,7 +21,7 @@ end
21
21
  def execute(context = {}, command)
22
22
  puts "Executing '#{command}'..."
23
23
  # TODO: path.split(' ') to bypass the shell when we're not using env vars
24
-
24
+
25
25
  # Run the command with the context in environment,
26
26
  # printing the output as it's generated
27
27
  IO.popen(context, command) do |io_stream|
@@ -1,23 +1,72 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../lib/sweatshop/queue-helper'
3
-
4
- def parse(payload = '', of_format:)
5
- of_format = of_format.downcase
6
- lib_file = "#{__dir__}/../lib/sweatshop/payload/#{of_format}.rb"
7
- if of_format != 'payload' && File.file?(lib_file)
8
- require_relative lib_file
9
- Object.const_get("#{of_format.capitalize}Payload").new payload
10
- else
11
- puts "Dropping bad format: #{of_format}"
12
- nil
2
+ require 'bundler/setup'
3
+ require 'json'
4
+ require 'ezmq'
5
+ require 'contracts'
6
+ require 'sweatshop/config'
7
+ require 'sweatshop/payload'
8
+
9
+ $stdout.sync = true
10
+ include Contracts
11
+
12
+ Contract String => Bool
13
+ def json?(string)
14
+ begin
15
+ JSON.load string
16
+ true
17
+ rescue JSON::ParserError => e
18
+ false
13
19
  end
14
20
  end
15
21
 
16
- QueueHelper.wait_for('raw-payload') do |data|
17
- puts "Parsing: #{data}"
18
- payload = parse data['payload'], of_format: data['format']
19
- if payload
20
- hash = { payload: payload.to_hash, job_name: data['job_name'] }
21
- QueueHelper.enqueue hash, to: 'parsed-payload'
22
- end
22
+ Contract String => Bool
23
+ def from_github?(user_agent)
24
+ user_agent.start_with? 'Github-Hookshot'
25
+ end
26
+
27
+ Contract String => Bool
28
+ def from_bitbucket?(user_agent)
29
+ user_agent.start_with? 'Bitbucket.org'
30
+ end
31
+
32
+ Contract Hash => String
33
+ def detect_format_of(request)
34
+ return 'empty' if request['payload'].empty?
35
+ return 'Github' if from_github? request['user_agent']
36
+ return 'Bitbucket' if from_bitbucket? request['user_agent']
37
+ return 'JSON' if json? request['payload']
38
+ 'unsupported'
39
+ end
40
+
41
+ Contract String, String => Or[Hash, nil]
42
+ def payload_hash_from(payload, format)
43
+ lib_file = "#{__dir__}/../lib/sweatshop/payload/#{format}.rb"
44
+ return nil unless File.file?(lib_file)
45
+ require_relative lib_file
46
+ Object.const_get("#{format}Payload").new(payload).to_hash
47
+ end
48
+
49
+ Contract Hash => Hash
50
+ def parse(request)
51
+ format = detect_format_of request
52
+ payload = payload_hash_from request['payload'], format
53
+ return {payload: {}, error: "Can't parse #{format} payload"} if payload.nil?
54
+ {payload: payload, error: ''}
55
+ end
56
+
57
+ Contract Any => Hash
58
+ def validate(request)
59
+ return {payload: {}, error: "Invalid JSON request"} unless json? request
60
+ request = JSON.load request
61
+ return {payload: {}, error: "Empty request"} if request.nil?
62
+ request
63
+ end
64
+
65
+ puts 'Started'
66
+ server = EZMQ::Server.new port: configatron.payload_parser_port#, encode: -> m { JSON.dump m }
67
+ server.listen do |request|
68
+ puts "Parsing: #{request}"
69
+ request = validate request
70
+ request = parse request unless request[:error]
71
+ JSON.dump request
23
72
  end
data/config.defaults.yaml CHANGED
@@ -8,3 +8,6 @@ moneta_directory: .robot_sweatshop/db
8
8
  # group: nogroup
9
9
  http_port: 8080
10
10
  http_bind: 0.0.0.0
11
+ # queue_handler_port: 5556
12
+ # queue_broadcaster_port: 5557
13
+ payload_parser_port: 5558
@@ -0,0 +1,9 @@
1
+ ---
2
+ branch_whitelist:
3
+ - develop
4
+
5
+ commands:
6
+ - echo $custom_env > test.txt
7
+
8
+ environment:
9
+ custom_env: success
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  # http://www.url-encode-decode.com/
3
- malformed: |
4
- payload=%7B%22nothing%22%3A%22here%22%7D
3
+ empty: ''
4
+ nonjson: not json
5
+ json: '{"test1": "value", "test2": "value"}'
5
6
  bitbucket: |
6
7
  payload=%7B%22repository%22%3A+%7B%22website%22%3A+%22%22%2C+%22fork%22%3A+false%2C+%22name%22%3A+%22Project+X%22%2C+%22scm%22%3A+%22git%22%2C+%22owner%22%3A+%22marcus%22%2C+%22absolute_url%22%3A+%22%2Fmarcus%2Fproject-x%2F%22%2C+%22slug%22%3A+%22project-x%22%2C+%22is_private%22%3A+true%7D%2C+%22truncated%22%3A+false%2C+%22commits%22%3A+%5B%7B%22node%22%3A+%22620ade18607a%22%2C+%22files%22%3A+%5B%7B%22type%22%3A+%22modified%22%2C+%22file%22%3A+%22README%22%7D%5D%2C+%22raw_author%22%3A+%22Marcus+Bertrand+%3Cmarcus%40somedomain.com%3E%22%2C+%22utctimestamp%22%3A+%222014-08-08+00%3A04%3A07%2B00%3A00%22%2C+%22author%22%3A+%22marcus%22%2C+%22timestamp%22%3A+%222014-08-08+02%3A04%3A07%22%2C+%22raw_node%22%3A+%22620ade18607ac42d872b568bb92acaa9a28620e9%22%2C+%22parents%22%3A+%5B%22702c70160afc%22%5D%2C+%22branch%22%3A+%22develop%22%2C+%22message%22%3A+%22Added+some+more+things+to+somefile.py%5Cn%22%2C+%22revision%22%3A+null%2C+%22size%22%3A+-1%7D%5D%2C+%22canon_url%22%3A+%22https%3A%2F%2Fbitbucket.org%22%2C+%22user%22%3A+%22marcus%22%7D
7
8
  github: |
@@ -1,10 +1,8 @@
1
1
  ---
2
- branch_whitelist:
3
- - develop
4
-
5
2
  commands:
6
3
  - echo $custom_env > test.txt
7
4
 
8
5
  environment:
9
6
  custom_env: success
10
- converts_to_string: { string: only }
7
+ some_hash: { string: only }
8
+ some_array: ['string', 'only']
@@ -11,18 +11,15 @@ describe 'Robot Sweatshop' do
11
11
 
12
12
  setup do
13
13
  @client = EZMQ::Client.new port: 5556
14
- @job_name = 'test_job'
15
14
  @test_file = reset_test_file
16
15
  clear_all_queues
17
16
  end
18
17
 
19
18
  context "POST git data to the HTTP Input" do
20
19
  setup do
21
- url = input_http_url for_job: @job_name
22
- puts "posting..."
23
- HTTP.post url, body: load_payload('bitbucket')
24
- puts "post successful"
25
- sleep $for_everything
20
+ url = input_http_url for_job: 'test_job'
21
+ HTTP.post url, body: example_raw_payload(of_format: 'JSON')
22
+ sleep $for_io_calls
26
23
  end
27
24
 
28
25
  should 'run jobs with the context as environment variables' do
@@ -13,32 +13,36 @@ given 'the HTTP Input' do
13
13
  setup do
14
14
  @subscriber = EZMQ::Subscriber.new port: 5557, topic: 'busy-queues'
15
15
  @client = EZMQ::Client.new port: 5556
16
- @job_name = 'test_job'
17
- @raw_payload_queue = 'raw-payload'
16
+ @payload_queue = 'payload'
18
17
  clear_all_queues
19
18
  end
20
-
21
- %w(Bitbucket).each do |git_host|
22
- context "POST data from #{git_host}" do
19
+
20
+ %w(Bitbucket Github JSON).each do |format|
21
+ context "POSTing #{format} data" do
23
22
  setup do
24
- url = input_http_url for_job: @job_name, in_format: git_host
25
- HTTP.post url, body: load_payload(git_host)
23
+ url = input_http_url for_job: 'test_job'
24
+ HTTP.post url, body: example_raw_payload(of_format: format)
26
25
  end
27
26
 
28
- should 'enqueue to \'raw-payload\'' do
27
+ should 'enqueue to \'payload\'' do
29
28
  Timeout.timeout($for_a_moment) do
30
29
  @subscriber.listen do |message, topic|
31
- break if message == @raw_payload_queue
30
+ break if message == @payload_queue
32
31
  end
33
32
  end
34
33
  end
35
34
 
36
- should 'enqueue payload details' do
37
- response = @client.request "mirror-#{@raw_payload_queue}"
35
+ should 'enqueue payload details and format' do
36
+ response = @client.request "mirror-#{@payload_queue}"
38
37
  data = JSON.parse response
39
- %w(payload format job_name).each do |type|
40
- assert_kind_of String, data[type]
41
- end
38
+ assert_kind_of String, data['payload']
39
+ assert_kind_of String, data['user_agent']
40
+ end
41
+
42
+ should 'enqueue job name' do
43
+ response = @client.request "mirror-#{@payload_queue}"
44
+ data = JSON.parse response
45
+ assert_kind_of String, data['job_name']
42
46
  end
43
47
  end
44
48
  end
@@ -6,73 +6,73 @@ require_relative 'shared/helpers'
6
6
 
7
7
  describe 'the Job Assembler' do
8
8
  include QueueHelper
9
- include PayloadHelper
9
+ include InHelper
10
10
  include JobHelper
11
11
 
12
12
  setup do
13
13
  @client = EZMQ::Client.new port: 5556
14
- @parsed_payloads_queue = 'parsed-payload'
14
+ @payloads_queue = 'payload'
15
15
  @jobs_queue = 'jobs'
16
16
  clear_all_queues
17
17
  end
18
18
 
19
- given 'valid parsed payload data in \'parsed-payload\'' do
20
- setup do
21
- payload = example_parsed_payload(for_branch: 'develop')
22
- @client.request "#{@parsed_payloads_queue} #{payload}"
23
- sleep $for_a_while
24
- end
19
+ %w(Git JSON).each do |request|
20
+ given "#{request} requests in \'payload\'" do
21
+ setup do
22
+ payload = example_job_request of_type: request
23
+ @client.request "#{@payloads_queue} #{payload}"
24
+ sleep $for_a_moment
25
+ end
25
26
 
26
- should 'remove it from \'parsed-payload\'' do
27
- response = @client.request @parsed_payloads_queue
28
- assert_equal '', response
29
- end
27
+ should 'remove the request from \'payload\'' do
28
+ response = @client.request @payloads_queue
29
+ assert_equal '', response
30
+ end
30
31
 
31
- should 'enqueue commands, context, and job name to \'jobs\'' do
32
- response = @client.request "mirror-#{@jobs_queue}"
33
- response = JSON.parse response
34
- assert_kind_of Hash, response['context']
35
- assert_kind_of Array, response['commands']
36
- assert_kind_of String, response['job_name']
37
- end
32
+ should 'enqueue commands, context, and job name to \'jobs\'' do
33
+ response = @client.request "mirror-#{@jobs_queue}"
34
+ response = JSON.load response
35
+ assert_kind_of Hash, response['context']
36
+ assert_kind_of Array, response['commands']
37
+ assert_kind_of String, response['job_name']
38
+ end
38
39
 
39
- should 'convert objects into JSON strings' do
40
- response = @client.request "mirror-#{@jobs_queue}"
41
- response = JSON.parse response
42
- assert_kind_of Hash, JSON.parse(response['context']['converts_to_string'])
43
- end
40
+ should 'store everything in the context as strings' do
41
+ response = @client.request "mirror-#{@jobs_queue}"
42
+ response = JSON.load response
43
+ response['context'].each { |_key, value| assert_kind_of String, value }
44
+ end
44
45
 
45
- should 'only enqueue string objects to context' do
46
- response = @client.request "mirror-#{@jobs_queue}"
47
- response = JSON.parse response
48
- response['context'].each do |_key, value|
49
- assert_kind_of String, value
46
+ should 'build the context with a parsed payload' do
47
+ response = @client.request "mirror-#{@jobs_queue}"
48
+ response = JSON.load response
49
+ assert_kind_of Hash, response['context']
50
+ if request == 'Git'
51
+ assert_equal 'develop', response['context']['branch']
52
+ else
53
+ assert_equal 'value', response['context']['test1']
54
+ end
50
55
  end
51
56
  end
52
57
  end
53
58
 
54
- given 'invalid job data in \'parsed-payload\'' do
55
- setup do
56
- invalid_data = {
57
- ignored_branch: example_parsed_payload(for_branch: 'not_on_whitelist'),
58
- bad_payload: example_parsed_payload(with_payload: 'not hash'),
59
- bad_job: example_parsed_payload(for_job: 'asdf'),
60
- not_json: 'not_json'
61
- }
62
- invalid_data.each do |_type, datum|
63
- @client.request "#{@parsed_payloads_queue} #{datum}"
59
+ %w(IgnoredBranch UnknownJob NonJSON).each do |request|
60
+ given "#{request} requests in \'payload\'" do
61
+ setup do
62
+ payload = example_job_request of_type: request
63
+ @client.request "#{@payloads_queue} #{payload}"
64
+ sleep $for_a_moment
64
65
  end
65
- sleep $for_a_while
66
- end
67
66
 
68
- should 'remove all of it from \'parsed-payload\'' do
69
- response = @client.request @parsed_payloads_queue
70
- assert_equal '', response
71
- end
67
+ should 'remove the request from \'payload\'' do
68
+ response = @client.request @payloads_queue
69
+ assert_equal '', response
70
+ end
72
71
 
73
- should 'not queue anything to \'jobs\'' do
74
- response = @client.request @jobs_queue
75
- assert_equal '', response
72
+ should 'not queue anything to \'jobs\'' do
73
+ response = @client.request @jobs_queue
74
+ assert_equal '', response
75
+ end
76
76
  end
77
77
  end
78
78
  end
@@ -1,70 +1,61 @@
1
1
  require 'kintama'
2
2
  require 'ezmq'
3
3
  require 'json'
4
+ require 'timeout'
4
5
  require_relative 'shared/process_spawning'
5
6
  require_relative 'shared/helpers'
6
7
 
7
8
  describe 'the Payload Parser' do
8
9
  include QueueHelper
9
10
  include InHelper
10
- include PayloadHelper
11
11
 
12
12
  setup do
13
- @client = EZMQ::Client.new port: 5556
14
- @raw_queue = 'raw-payload'
15
- @parsed_queue = 'parsed-payload'
16
- clear_all_queues
13
+ @client = EZMQ::Client.new port: configatron.payload_parser_port
17
14
  end
18
15
 
19
- %w(Bitbucket Github).each do |format|
20
- given "valid #{format} data in 'raw-payload'" do
16
+ %w(Bitbucket Github JSON).each do |format|
17
+ given "valid #{format} payloads" do
21
18
  setup do
22
- payload = example_raw_payload(with_format: format)
23
- @client.request "#{@raw_queue} #{payload}"
24
- sleep $for_a_while
25
- end
26
-
27
- should 'remove it from \'raw-payload\'' do
28
- response = @client.request @raw_queue
29
- assert_equal '', response
19
+ payload = example_payload_request of_format: format
20
+ @response = Timeout.timeout($for_a_while) do
21
+ @client.request JSON.dump(payload)
22
+ end
30
23
  end
31
24
 
32
- should 'enqueue parsed payload data and job name to \'parsed-payload\'' do
33
- response = @client.request "mirror-#{@parsed_queue}"
34
- response = JSON.parse response
25
+ should 'return a parsed payload object' do
26
+ @response = JSON.parse @response
27
+ assert_equal true, @response['error'].empty?
28
+ assert_kind_of Hash, @response['payload']
35
29
 
36
- assert_kind_of Hash, response['payload']
37
- Payload.hash_keys.each do |key|
38
- assert_not_nil response['payload'][key]
39
- assert_not_equal key, response['payload'][key] # important for how Ruby interprets "string"['key']
30
+ keys = case format
31
+ when 'JSON'
32
+ %w(test1 test2)
33
+ else
34
+ Payload.hash_keys
35
+ end
36
+ keys.each do |key|
37
+ payload = @response['payload']
38
+ assert_not_nil payload[key]
39
+ assert_not_equal key, payload[key] # catches "string"[key]
40
40
  end
41
- assert_kind_of String, response['job_name']
42
41
  end
43
42
  end
44
43
  end
45
44
 
46
- given 'invalid payload data in \'raw-payload\'' do
47
- setup do
48
- invalid_data = {
49
- malformed_payload: example_raw_payload(with_format: 'malformed'),
50
- unsupported_format: example_raw_payload(with_format: 'asdf'),
51
- not_json: 'not_json'
52
- }
53
- invalid_data.each do |_type, datum|
54
- @client.request "#{@raw_queue} #{datum}"
45
+ %w(Empty NonJSON).each do |format|
46
+ given "#{format} payloads" do
47
+ setup do
48
+ payload = example_raw_payload of_format: format
49
+ @response = Timeout.timeout($for_a_while) do
50
+ @client.request "#{payload}"
51
+ end
55
52
  end
56
- sleep $for_a_while
57
- # TODO: should not crash the payload parser
58
- end
59
53
 
60
- should 'remove all of it from \'raw-payload\'' do
61
- response = @client.request @raw_queue
62
- assert_equal '', response
63
- end
64
-
65
- should 'not queue anything to \'parsed-payload\'' do
66
- response = @client.request @parsed_queue
67
- assert_equal '', response
54
+ should 'return an error object' do
55
+ @response = JSON.load(@response)
56
+ assert_kind_of String, @response['error']
57
+ assert_equal true, @response['payload'].empty?
58
+ end
68
59
  end
69
60
  end
70
61
  end
@@ -1,8 +1,9 @@
1
+ require 'bundler/setup'
1
2
  require 'yaml'
2
3
  require 'json'
3
- require_relative '../../lib/sweatshop/moneta-queue'
4
- require_relative '../../lib/sweatshop/payload/payload'
5
- require_relative '../../lib/sweatshop/config'
4
+ require 'sweatshop/moneta-queue'
5
+ require 'sweatshop/payload'
6
+ require 'sweatshop/config'
6
7
 
7
8
  module QueueHelper
8
9
  def clear_all_queues
@@ -18,26 +19,50 @@ module QueueHelper
18
19
  end
19
20
 
20
21
  module InHelper
21
- def load_payload(of_format)
22
+ def example_raw_payload(of_format:)
22
23
  payload_strings = YAML.load_file "#{__dir__}/../data/payload_data.yaml"
23
24
  payload_strings[of_format.downcase]
24
25
  end
25
- def example_raw_payload(with_format:)
26
- payload = load_payload with_format
27
- JSON.generate payload: payload,
28
- format: with_format,
29
- job_name: 'test_job'
26
+ def input_http_url(for_job: 'test_job')
27
+ "http://localhost:#{configatron.http_port}/payload-for/#{for_job}"
30
28
  end
31
- def input_http_url(for_job: 'test_job', in_format: 'bitbucket')
32
- "http://localhost:#{configatron.http_port}/#{in_format}/payload-for/#{for_job}"
29
+ def user_agent_for(format)
30
+ case format
31
+ when 'Bitbucket'
32
+ 'Bitbucket.org'
33
+ when 'Github'
34
+ 'Github-Hookshot'
35
+ else
36
+ 'SomeRandomUserAgent'
37
+ end
33
38
  end
34
- end
35
-
36
- module PayloadHelper
37
- def example_parsed_payload(with_payload: nil, for_branch: 'develop', for_job: 'test_job')
38
- payload = with_payload || { branch: for_branch }
39
- JSON.generate payload: payload,
40
- job_name: for_job
39
+ def example_payload_request(of_format:)
40
+ {
41
+ payload: example_raw_payload(of_format: of_format),
42
+ user_agent: user_agent_for(of_format)
43
+ }
44
+ end
45
+ def job_request_data(type)
46
+ case type
47
+ when 'Git'
48
+ ['Bitbucket', 'git_job'] # develop branch
49
+ when 'JSON'
50
+ ['JSON', 'test_job']
51
+ when 'IgnoredBranch'
52
+ ['Github', 'git_job'] # master branch
53
+ when 'UnknownJob'
54
+ ['Bitbucket', 'unknown_job']
55
+ when 'NonJSON'
56
+ ['JSON', 'git_job', 'not json']
57
+ else
58
+ ['I', 'AM', 'ERROR']
59
+ end
60
+ end
61
+ def example_job_request(of_type:)
62
+ format, job_name, payload = job_request_data of_type
63
+ JSON.generate payload: payload || example_raw_payload(of_format: format),
64
+ job_name: job_name,
65
+ user_agent: user_agent_for(format)
41
66
  end
42
67
  end
43
68
 
@@ -1,15 +1,15 @@
1
1
  $for_a_moment = 0.1
2
2
  $for_a_while = 0.5
3
3
  $for_io_calls = 1
4
- $for_everything = 3
4
+ $for_everything = 2
5
5
 
6
6
  Kintama.on_start do
7
7
  puts `#{__dir__}/../../bin/sweatshop start --testing`
8
8
  FileUtils.cp "#{__dir__}/../data/test_job.yaml", File.expand_path(configatron.job_directory)
9
+ FileUtils.cp "#{__dir__}/../data/git_job.yaml", File.expand_path(configatron.job_directory)
9
10
  sleep $for_everything
10
11
  end
11
12
 
12
13
  Kintama.on_finish do
13
14
  puts `#{__dir__}/../../bin/sweatshop stop`
14
- # FileUtils.rm "#{configatron.job_directory}/test_job.yaml"
15
15
  end
data/lib/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  Job lifecycle:
2
2
 
3
3
  ```
4
- in-* -> raw-payloads { payload:, format:, job_name: } ->
5
- payload-parser -> parsed-payloads { payload:, job_name: } ->
4
+ in-* -> payloads { payload:, user_agent:, job_name: } ->
6
5
  job-assembler -> jobs { context:, commands:, job_name: } ->
7
6
  job-worker
8
7
  ```
@@ -10,3 +9,13 @@ job-worker
10
9
  All passing done via the moneta core in queue/*
11
10
 
12
11
  Also queue/watcher for debugging
12
+
13
+ ---
14
+
15
+ The Job Assembler also uses the Payload Parser service:
16
+
17
+ ```
18
+ { payload:, user_agent: } -> payload-parser -> { payload:, error: }
19
+ ```
20
+
21
+ Error is an empty string when it's successful. Otherwise it details what went wrong.
@@ -1,7 +1,7 @@
1
1
  require_relative '../config'
2
2
 
3
3
  def default_job
4
- "---\nbranch_whitelist:\n- master\n\ncommands:\n- echo 'Hello $WORLD!'\n\nenvironment:\n WORLD: Earth\n"
4
+ "---\n# branch_whitelist:\n# - master\n\ncommands:\n- echo \"Hello $WORLD!\"\n\nenvironment:\n WORLD: Earth\n"
5
5
  end
6
6
 
7
7
  def get_job_path(for_job: nil)
@@ -0,0 +1,4 @@
1
+ require_relative 'cli/common'
2
+ require_relative 'cli/config'
3
+ require_relative 'cli/job'
4
+ require_relative 'cli/start'
@@ -16,7 +16,7 @@ class MonetaQueue
16
16
  end
17
17
 
18
18
  def self.watched_queues
19
- %w(raw-payload parsed-payload jobs testing)
19
+ %w(payload parsed-payload jobs testing)
20
20
  end
21
21
 
22
22
  def enqueue(item)
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+ require_relative 'payload'
3
+
4
+ # A parser for arbitrary data
5
+ class JSONPayload < Payload
6
+ def initialize(payload)
7
+ @payload = JSON.parse payload || {}
8
+ end
9
+
10
+ def to_hash
11
+ @payload
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'payload/payload'
2
+ require_relative 'payload/bitbucket'
3
+ require_relative 'payload/github'
4
+ require_relative 'payload/json'
data/lib/sweatshop.rb CHANGED
@@ -1,16 +1,5 @@
1
1
  require_relative 'sweatshop/moneta-queue'
2
2
  require_relative 'sweatshop/queue-helper'
3
3
  require_relative 'sweatshop/config'
4
-
5
- require_relative 'sweatshop/payload/payload'
6
- require_relative 'sweatshop/payload/bitbucket'
7
- require_relative 'sweatshop/payload/github'
8
-
9
- require_relative 'sweatshop/cli/common'
10
- require_relative 'sweatshop/cli/config'
11
- require_relative 'sweatshop/cli/inspect'
12
- require_relative 'sweatshop/cli/job'
13
- require_relative 'sweatshop/cli/setup'
14
- require_relative 'sweatshop/cli/start'
15
-
16
-
4
+ require_relative 'sweatshop/payload'
5
+ require_relative 'sweatshop/cli'
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'robot_sweatshop'
3
- gem.version = '0.2.1'
3
+ gem.version = '0.3.0'
4
4
  gem.licenses = 'MIT'
5
5
  gem.authors = ['Justin Scott']
6
6
  gem.email = 'jvscott@gmail.com'
@@ -20,9 +20,10 @@ Gem::Specification.new do |gem|
20
20
  gem.add_runtime_dependency 'faker'
21
21
  gem.add_runtime_dependency 'commander'
22
22
  gem.add_runtime_dependency 'eye'
23
- gem.add_runtime_dependency 'colorize'
23
+ gem.add_runtime_dependency 'colorize' # TODO: replace with rainbow
24
24
  gem.add_runtime_dependency 'configatron'
25
25
  gem.add_runtime_dependency 'moneta'
26
+ gem.add_runtime_dependency 'contracts'
26
27
 
27
28
  gem.add_development_dependency 'rake'
28
29
  gem.add_development_dependency 'kintama'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robot_sweatshop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Scott
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-24 00:00:00.000000000 Z
11
+ date: 2015-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: contracts
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rake
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -236,6 +250,7 @@ files:
236
250
  - bin/sweatshop-queue-watcher
237
251
  - config.defaults.yaml
238
252
  - kintama/README.md
253
+ - kintama/data/git_job.yaml
239
254
  - kintama/data/payload_data.yaml
240
255
  - kintama/data/test_job.yaml
241
256
  - kintama/end-to-end_spec.rb
@@ -251,6 +266,7 @@ files:
251
266
  - kintama/shared/process_spawning.rb
252
267
  - lib/README.md
253
268
  - lib/sweatshop.rb
269
+ - lib/sweatshop/cli.rb
254
270
  - lib/sweatshop/cli/common.rb
255
271
  - lib/sweatshop/cli/config.rb
256
272
  - lib/sweatshop/cli/job.rb
@@ -258,8 +274,10 @@ files:
258
274
  - lib/sweatshop/config.rb
259
275
  - lib/sweatshop/create-config-directories.rb
260
276
  - lib/sweatshop/moneta-queue.rb
277
+ - lib/sweatshop/payload.rb
261
278
  - lib/sweatshop/payload/bitbucket.rb
262
279
  - lib/sweatshop/payload/github.rb
280
+ - lib/sweatshop/payload/json.rb
263
281
  - lib/sweatshop/payload/payload.rb
264
282
  - lib/sweatshop/queue-helper.rb
265
283
  - robot_sweatshop.eye
@@ -291,6 +309,7 @@ signing_key:
291
309
  specification_version: 4
292
310
  summary: Robot Sweatshop
293
311
  test_files:
312
+ - kintama/data/git_job.yaml
294
313
  - kintama/data/payload_data.yaml
295
314
  - kintama/data/test_job.yaml
296
315
  - kintama/shared/helpers.rb