robot_sweatshop 0.1.2
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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +45 -0
- data/Rakefile +3 -0
- data/bin/lib/common.rb +49 -0
- data/bin/lib/config.rb +20 -0
- data/bin/lib/inspect.rb +42 -0
- data/bin/lib/job.rb +13 -0
- data/bin/lib/start.rb +11 -0
- data/bin/sweatshop +73 -0
- data/config.rb +24 -0
- data/config.yaml +12 -0
- data/jobs/example.yaml +10 -0
- data/kintama/README.md +3 -0
- data/kintama/data/payload_data.yaml +6 -0
- data/kintama/end-to-end_spec.rb +30 -0
- data/kintama/input_http_spec.rb +45 -0
- data/kintama/job_assembler_spec.rb +72 -0
- data/kintama/job_worker_spec.rb +65 -0
- data/kintama/moneta-queue_spec.rb +48 -0
- data/kintama/payload_parser_spec.rb +71 -0
- data/kintama/queue_broadcaster_spec.rb +39 -0
- data/kintama/queue_handler_spec.rb +54 -0
- data/kintama/run_all.rb +3 -0
- data/kintama/shared/helpers.rb +55 -0
- data/kintama/shared/process_spawning.rb +13 -0
- data/lib/README.md +12 -0
- data/lib/input/http.rb +27 -0
- data/lib/job/assembler.rb +36 -0
- data/lib/job/worker.rb +40 -0
- data/lib/payload/lib/bitbucket.rb +61 -0
- data/lib/payload/lib/github.rb +45 -0
- data/lib/payload/lib/payload.rb +12 -0
- data/lib/payload/parser.rb +23 -0
- data/lib/queue-helper.rb +32 -0
- data/lib/queue/broadcaster.rb +18 -0
- data/lib/queue/handler.rb +23 -0
- data/lib/queue/lib/moneta-queue.rb +49 -0
- data/lib/queue/watcher.rb +18 -0
- data/robot_sweatshop.eye +58 -0
- data/robot_sweatshop.gemspec +26 -0
- data/robot_sweatshop.production.eye +8 -0
- data/robot_sweatshop.testing.eye +8 -0
- data/workspaces/.keep +0 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1eec24a1025b81640f28412b5601d5999e56040f
|
4
|
+
data.tar.gz: 9e3efeb22f6e15c8ec7dcf8c60760901eaf8be34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72eb1ee064569d102f7ac6f1e0a2b772dea5bd790b52f963793ca161bca22fb5a5a5edbad0082e9ffc7fe9d0a9378269ad55b943632b02d5018a889326fd9ef9
|
7
|
+
data.tar.gz: 6d0680dd1d160940db79f436240451489e843f8c0af4a127e00d38ec4413db15e2f57ba3e81e89f3fc8fc4cf2be75ef254a6412ed7065267e58253d4c1986849
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Justin Scott
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Robot Sweatshop
|
2
|
+
|
3
|
+
[Jenkins](http://jenkins-ci.org/) is horrible to maintain and is problematic when automating installation and configuration. [Drone](https://drone.io/) assumes that you use Docker. [Travis-CI](https://travis-ci.org/recent) is difficult to self-host. All of these frameworks are highly opinionated in one way or another, forcing you to do things _their_ way.
|
4
|
+
|
5
|
+
Robot Sweatshop is a single-purpose CI server that runs collections of arbitrary scripts when it needs to, usually when new code is pushed. There's no assumptions about what you want to report, what front-end you need, or even what repositories you want to clone because you can do that better than I can. It's just you, your code, and the scripts that test and deploy it.
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install robot_sweatshop
|
11
|
+
sweatshop start
|
12
|
+
```
|
13
|
+
|
14
|
+
Robot Sweatshop uses Eye to handle its services and will set up and configure everything for you.
|
15
|
+
|
16
|
+
After configuring a job, POST a payload to `localhost:8080/:format/payload-for/:job`. For example, triggering a Bitbucket Git POST hook on `localhost:8080/bitbucket/payload-for/example` will parse the payload and run the 'example' job with the payload data in the environment.
|
17
|
+
|
18
|
+
You can see what jobs are available with `sweatshop job --list`.
|
19
|
+
|
20
|
+
Currently supported formats:
|
21
|
+
|
22
|
+
- bitbucket
|
23
|
+
|
24
|
+
# Configuration
|
25
|
+
|
26
|
+
The server isn't much help without a job to run. Run `sudo -E sweatshop job <name>` to create a new job or edit an existing one.
|
27
|
+
|
28
|
+
You can also use `sudo -E sweatshop config` to create and edit a user configuration at `/etc/robot_sweatshop/config.yaml`.
|
29
|
+
|
30
|
+
Not sure if your job is valid? Run `sweatshop job --inspection <name>` to see if there's something you overlooked.
|
31
|
+
|
32
|
+
# Security
|
33
|
+
|
34
|
+
_TODO: Support for running as a custom user via eye uid/gid_
|
35
|
+
|
36
|
+
# Roadmap
|
37
|
+
|
38
|
+
- Enable Github support
|
39
|
+
- CLI job running
|
40
|
+
- Common scrips such as git repo syncing
|
41
|
+
- Support for multiple workers
|
42
|
+
- Better logging for the processes
|
43
|
+
- Improved architecture:
|
44
|
+
|
45
|
+

|
data/Rakefile
ADDED
data/bin/lib/common.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
def notify(type = :success, string)
|
4
|
+
color = case type
|
5
|
+
when :success
|
6
|
+
:green
|
7
|
+
when :failure
|
8
|
+
:red
|
9
|
+
when :warning
|
10
|
+
:yellow
|
11
|
+
when :info
|
12
|
+
:light_blue
|
13
|
+
else
|
14
|
+
''
|
15
|
+
end
|
16
|
+
puts "[#{type.to_s.capitalize.colorize(color)}] #{string}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_job_file(for_job:)
|
20
|
+
for_job = "#{__dir__}/../../jobs/#{for_job}.yaml"
|
21
|
+
if for_job.nil?
|
22
|
+
notify :failure, 'Please specify the job to create or edit. See --help for details'
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
File.expand_path for_job
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def edit(file)
|
30
|
+
edit = ENV['EDITOR']
|
31
|
+
if edit
|
32
|
+
notify :info, "Manually editing file '#{file}'"
|
33
|
+
system edit, file
|
34
|
+
else
|
35
|
+
notify :failure, "No editor specified in $EDITOR environment variable"
|
36
|
+
notify :info, "Displaying file contents instead"
|
37
|
+
system 'cat', file
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_and_edit(file, with_default: '')
|
42
|
+
file = File.expand_path file
|
43
|
+
FileUtils.mkdir_p File.dirname(file)
|
44
|
+
unless File.file?(file)
|
45
|
+
File.write file, with_default
|
46
|
+
notify :success, "Created new file '#{file}'"
|
47
|
+
end
|
48
|
+
edit file
|
49
|
+
end
|
data/bin/lib/config.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
require_relative '../../config'
|
4
|
+
|
5
|
+
def default_config
|
6
|
+
File.read "#{__dir__}/../../config.yaml"
|
7
|
+
end
|
8
|
+
|
9
|
+
def user_config_file
|
10
|
+
'/etc/robot_sweatshop/config.yaml'
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_user_config
|
14
|
+
FileUtils.rm_rf user_config_file
|
15
|
+
notify :success, 'Removing current user config file'
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_and_edit_user_config
|
19
|
+
create_and_edit user_config_file, with_default: default_config
|
20
|
+
end
|
data/bin/lib/inspect.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
def valid_lists?(job)
|
2
|
+
errors = false
|
3
|
+
%w(branch_whitelist commands).each do |list|
|
4
|
+
if job[list].nil? || job[list].empty?
|
5
|
+
notify :failure, "#{list} empty or not found"
|
6
|
+
errors = true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
errors
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid_environment?(job)
|
13
|
+
errors = false
|
14
|
+
job['environment'].each do |key, value|
|
15
|
+
unless value.is_a? String
|
16
|
+
notify :warning, "Non-string value for '#{key}'"
|
17
|
+
errors = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
errors
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid_yaml?(job)
|
24
|
+
if job
|
25
|
+
true
|
26
|
+
else
|
27
|
+
notify :failure, "Invalid YAML"
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate(job_file)
|
33
|
+
unless File.file?(job_file)
|
34
|
+
notify :failure, 'Job not found. Create it with \'workshop job\''
|
35
|
+
else
|
36
|
+
job = YAML.load_file job_file
|
37
|
+
return unless valid_yaml?(job)
|
38
|
+
errors = valid_lists?(job) |
|
39
|
+
valid_environment?(job)
|
40
|
+
notify :success, 'Valid job configuration' unless errors
|
41
|
+
end
|
42
|
+
end
|
data/bin/lib/job.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'common'
|
2
|
+
|
3
|
+
def empty_job
|
4
|
+
"---\nbranch_whitelist:\n- master\n\ncommands:\n- echo 'Hello $WORLD!'\n\nenvironment:\n WORLD: Earth\n"
|
5
|
+
end
|
6
|
+
|
7
|
+
def create_and_edit_job(job_path)
|
8
|
+
create_and_edit job_path, with_default: empty_job
|
9
|
+
end
|
10
|
+
|
11
|
+
def list_jobs
|
12
|
+
puts Dir.glob("#{__dir__}/../../jobs/*").map { |file| File.basename(file, '.yaml') }
|
13
|
+
end
|
data/bin/lib/start.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
def start_sweatshop(for_environment:)
|
2
|
+
eye_config = "#{__dir__}/../../robot_sweatshop.#{for_environment}.eye"
|
3
|
+
output = `eye load #{eye_config}`
|
4
|
+
if $?.exitstatus != 0
|
5
|
+
notify :failure, output
|
6
|
+
else
|
7
|
+
notify :success, "Robot Sweatshop loaded with a #{for_environment} configuration"
|
8
|
+
notify :info, `eye restart robot_sweatshop`
|
9
|
+
puts 'Check \'eye --help\' for more info on debugging'
|
10
|
+
end
|
11
|
+
end
|
data/bin/sweatshop
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'yaml'
|
3
|
+
require 'commander/import'
|
4
|
+
require 'colorize'
|
5
|
+
require_relative 'lib/common'
|
6
|
+
require_relative 'lib/job'
|
7
|
+
require_relative 'lib/inspect'
|
8
|
+
require_relative 'lib/start'
|
9
|
+
require_relative 'lib/config'
|
10
|
+
require_relative '../config'
|
11
|
+
|
12
|
+
program :name, 'Robot Sweatshop'
|
13
|
+
program :version, '0.1.1'
|
14
|
+
program :description, 'A lightweight, unopinionated CI server'
|
15
|
+
program :help, 'Author', 'Justin Scott <jvscott@gmail.com>'
|
16
|
+
|
17
|
+
command :job do |c|
|
18
|
+
c.syntax = 'sweatshop job <name>'
|
19
|
+
c.description = 'Creates and edits jobs.'
|
20
|
+
c.option '--list', 'List all available jobs.'
|
21
|
+
c.option '--inspection', 'Inspect the given job.'
|
22
|
+
c.action do |args, options|
|
23
|
+
options.default :list => false
|
24
|
+
options.default :inspect => false
|
25
|
+
|
26
|
+
job_file = get_job_file for_job: args.first
|
27
|
+
if options.list
|
28
|
+
list_jobs
|
29
|
+
elsif options.inspection
|
30
|
+
validate job_file unless job_file.nil?
|
31
|
+
else
|
32
|
+
create_and_edit_job job_file unless job_file.nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
command :start do |c|
|
38
|
+
c.syntax = 'sweatshop start [options]'
|
39
|
+
c.description = 'Start the Sweatshop.'
|
40
|
+
c.option '--testing', 'Load the testing Eye configuration.'
|
41
|
+
c.action do |args, options|
|
42
|
+
options.default :testing => false
|
43
|
+
environment = options.testing ? 'testing' : 'production'
|
44
|
+
start_sweatshop for_environment: environment
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
command :stop do |c|
|
49
|
+
c.syntax = 'sweatshop stop'
|
50
|
+
c.description = 'Stop the Sweatshop.'
|
51
|
+
c.action do
|
52
|
+
notify :info, `eye stop robot_sweatshop`
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
command :directory do |c|
|
57
|
+
c.syntax = 'sweatshop directory'
|
58
|
+
c.description = 'Return the path that Robot Sweatshop runs from.'
|
59
|
+
c.action do
|
60
|
+
puts File.expand_path("#{__dir__}/..")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
command :config do |c|
|
65
|
+
c.syntax = 'sweatshop config [options]'
|
66
|
+
c.description = 'Creates and edits the user configuration file.'
|
67
|
+
c.option '--reset', 'Reset to factory defaults before editing.'
|
68
|
+
c.action do |args, options|
|
69
|
+
options.default :reset => false
|
70
|
+
remove_user_config if options.reset
|
71
|
+
create_and_edit_user_config
|
72
|
+
end
|
73
|
+
end
|
data/config.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'configatron'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
configurations = [
|
6
|
+
"#{__dir__}/config.yaml",
|
7
|
+
"#{__dir__}/config.user.yaml",
|
8
|
+
"/etc/robot_sweatshop/config.yaml"
|
9
|
+
]
|
10
|
+
configurations.each do |config_path|
|
11
|
+
if File.file? config_path
|
12
|
+
hash = YAML.load_file config_path
|
13
|
+
configatron.configure_from_hash hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
config_directories = [
|
18
|
+
configatron.common.logfile_directory,
|
19
|
+
configatron.common.pidfile_directory,
|
20
|
+
configatron.queue.moneta_directory
|
21
|
+
]
|
22
|
+
config_directories.each do |directory|
|
23
|
+
FileUtils.mkdir_p directory unless Dir.exists? directory
|
24
|
+
end
|
data/config.yaml
ADDED
data/jobs/example.yaml
ADDED
data/kintama/README.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
---
|
2
|
+
# http://www.url-encode-decode.com/
|
3
|
+
malformed: |
|
4
|
+
payload=%7B%22nothing%22%3A%22here%22%7D
|
5
|
+
bitbucket: |
|
6
|
+
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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'kintama'
|
2
|
+
require 'ezmq'
|
3
|
+
require 'http'
|
4
|
+
require_relative 'shared/process_spawning'
|
5
|
+
require_relative 'shared/helpers'
|
6
|
+
|
7
|
+
describe 'Robot Sweatshop' do
|
8
|
+
include QueueHelper
|
9
|
+
include InHelper
|
10
|
+
include JobHelper
|
11
|
+
|
12
|
+
setup do
|
13
|
+
@client = EZMQ::Client.new port: 5556
|
14
|
+
@job_name = 'example'
|
15
|
+
@test_file = reset_test_file
|
16
|
+
clear_all_queues
|
17
|
+
end
|
18
|
+
|
19
|
+
context "POST git data to the HTTP Input" do
|
20
|
+
setup do
|
21
|
+
url = input_http_url for_job: @job_name
|
22
|
+
HTTP.post url, body: load_payload('bitbucket')
|
23
|
+
sleep $for_everything
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'run jobs with the context as environment variables' do
|
27
|
+
assert_equal "success\n", File.read(@test_file)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'kintama'
|
2
|
+
require 'ezmq'
|
3
|
+
require 'json'
|
4
|
+
require 'timeout'
|
5
|
+
require 'http'
|
6
|
+
require_relative 'shared/process_spawning'
|
7
|
+
require_relative 'shared/helpers'
|
8
|
+
|
9
|
+
given 'the HTTP Input' do
|
10
|
+
include QueueHelper
|
11
|
+
include InHelper
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@subscriber = EZMQ::Subscriber.new port: 5557, topic: 'busy-queues'
|
15
|
+
@client = EZMQ::Client.new port: 5556
|
16
|
+
@job_name = 'example'
|
17
|
+
@raw_payload_queue = 'raw-payload'
|
18
|
+
clear_all_queues
|
19
|
+
end
|
20
|
+
|
21
|
+
%w(Bitbucket).each do |git_host|
|
22
|
+
context "POST data from #{git_host}" do
|
23
|
+
setup do
|
24
|
+
url = input_http_url for_job: @job_name, in_format: git_host
|
25
|
+
HTTP.post url, body: load_payload(git_host)
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'enqueue to \'raw-payload\'' do
|
29
|
+
Timeout.timeout($for_a_moment) do
|
30
|
+
@subscriber.listen do |message, topic|
|
31
|
+
break if message == @raw_payload_queue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'enqueue payload details' do
|
37
|
+
response = @client.request "mirror-#{@raw_payload_queue}"
|
38
|
+
data = JSON.parse response
|
39
|
+
%w(payload format job_name).each do |type|
|
40
|
+
assert_kind_of String, data[type]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|