robot_sweatshop 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rubocop.yml +12 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +21 -0
  6. data/README.md +45 -0
  7. data/Rakefile +3 -0
  8. data/bin/lib/common.rb +49 -0
  9. data/bin/lib/config.rb +20 -0
  10. data/bin/lib/inspect.rb +42 -0
  11. data/bin/lib/job.rb +13 -0
  12. data/bin/lib/start.rb +11 -0
  13. data/bin/sweatshop +73 -0
  14. data/config.rb +24 -0
  15. data/config.yaml +12 -0
  16. data/jobs/example.yaml +10 -0
  17. data/kintama/README.md +3 -0
  18. data/kintama/data/payload_data.yaml +6 -0
  19. data/kintama/end-to-end_spec.rb +30 -0
  20. data/kintama/input_http_spec.rb +45 -0
  21. data/kintama/job_assembler_spec.rb +72 -0
  22. data/kintama/job_worker_spec.rb +65 -0
  23. data/kintama/moneta-queue_spec.rb +48 -0
  24. data/kintama/payload_parser_spec.rb +71 -0
  25. data/kintama/queue_broadcaster_spec.rb +39 -0
  26. data/kintama/queue_handler_spec.rb +54 -0
  27. data/kintama/run_all.rb +3 -0
  28. data/kintama/shared/helpers.rb +55 -0
  29. data/kintama/shared/process_spawning.rb +13 -0
  30. data/lib/README.md +12 -0
  31. data/lib/input/http.rb +27 -0
  32. data/lib/job/assembler.rb +36 -0
  33. data/lib/job/worker.rb +40 -0
  34. data/lib/payload/lib/bitbucket.rb +61 -0
  35. data/lib/payload/lib/github.rb +45 -0
  36. data/lib/payload/lib/payload.rb +12 -0
  37. data/lib/payload/parser.rb +23 -0
  38. data/lib/queue-helper.rb +32 -0
  39. data/lib/queue/broadcaster.rb +18 -0
  40. data/lib/queue/handler.rb +23 -0
  41. data/lib/queue/lib/moneta-queue.rb +49 -0
  42. data/lib/queue/watcher.rb +18 -0
  43. data/robot_sweatshop.eye +58 -0
  44. data/robot_sweatshop.gemspec +26 -0
  45. data/robot_sweatshop.production.eye +8 -0
  46. data/robot_sweatshop.testing.eye +8 -0
  47. data/workspaces/.keep +0 -0
  48. 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
@@ -0,0 +1,6 @@
1
+ config.user.yaml
2
+ *.gem
3
+ jobs/*.yaml
4
+ workspaces/**/*
5
+ !workspaces/.keep
6
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ Exclude:
3
+ - kintama/**/*
4
+
5
+ Style/Encoding:
6
+ Enabled: false
7
+
8
+ Metrics/LineLength:
9
+ Max: 80
10
+
11
+ Style/FileName:
12
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
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
+ ![Improved architecture diagram](http://40.media.tumblr.com/8a5b6ca59c0d93c4ce6fc6b733932a5f/tumblr_nko478zp9N1qh941oo1_1280.jpg)
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ task :test do
2
+ require_relative 'kintama/run_all'
3
+ end
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
@@ -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
@@ -0,0 +1,12 @@
1
+ common:
2
+ pidfile_directory: /var/run/robot_sweatshop
3
+ logfile_directory: /var/log/robot_sweatshop
4
+ user_config_directory: /etc/robot_sweatshop
5
+
6
+ input:
7
+ http:
8
+ port: 8080
9
+ bind: 0.0.0.0
10
+
11
+ queue:
12
+ moneta_directory: /var/db/robot_sweatshop
data/jobs/example.yaml ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ branch_whitelist:
3
+ - develop
4
+
5
+ commands:
6
+ - echo $custom_env > test.txt
7
+
8
+ environment:
9
+ custom_env: success
10
+ converts_to_string: { string: only }
data/kintama/README.md ADDED
@@ -0,0 +1,3 @@
1
+ You can run individual `_spec.rb` files with Ruby or `run_all.rb` to go through each.
2
+
3
+ _Warning_: You must run http-in manually. Sinatra is a pain to start and stop programmatically. All other processes will be started by the helper.
@@ -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