robot_sweatshop 0.4.6 → 0.4.7

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: acc02c9c05d562880ac2ae2856bcb5697395ca1f
4
- data.tar.gz: df5ffe594c0e6c3f0af75d1a96b10fe7ef6ced63
3
+ metadata.gz: 7f1852d96c663c8d61e07123a2bf84162ba15698
4
+ data.tar.gz: 5b592ff4da66bb363625e9beeeb3dd0c64c8e85c
5
5
  SHA512:
6
- metadata.gz: 4b442e68f7963fb3f9b681d445e4d88ba0b9940a7a556506a9bac03a031cf7853c6e3385e424ca28df2356164444a8aa4ecffa04ff7cedb83d0faed15bf26cfc
7
- data.tar.gz: 482a8ccdb5cc3be30969a5a671ea4f82e0aed3dfb20f2979bfda1a127d015308b5b1da5d74eaa7c29d2aae21a331aeb64cafd46c5d6ba4fc14c5027ceab055d0
6
+ metadata.gz: ecdb8c16cbee4b20c667745c251953edc3850b59110ed850413a62c9454102c7455021c599af4b3b780f5acf3488d93b3d88c04d48877be6cd57b5c4a488aca8
7
+ data.tar.gz: ee78994a812f67a8623051e77f4252ea3c8b8f9746230861494e74f18bdb9a2718ab76dc4552b5eb296244993879b70b61980b74cba1e147310e683f651e459e
data/.rubocop.yml CHANGED
@@ -7,7 +7,7 @@ Style/Encoding:
7
7
  Enabled: false
8
8
 
9
9
  Metrics/LineLength:
10
- Max: 80
10
+ Max: 90
11
11
 
12
12
  Style/FileName:
13
13
  Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- robot_sweatshop (0.4.5)
4
+ robot_sweatshop (0.4.6)
5
5
  bundler
6
6
  commander
7
7
  configatron
data/README.md CHANGED
@@ -40,13 +40,13 @@ You probably don't want to run Robot Sweatshop as a sudo user. Create a testing
40
40
 
41
41
  1.0
42
42
 
43
- - Docs on architecture
44
- - Easier way to run multiple workers
45
43
  - Mascot
46
44
 
47
45
  1.1
48
46
 
49
- - push/pull out node that worker streams output to
47
+ - Easier way to run multiple workers
48
+ - Push/pull out node that worker streams output to
49
+ - Add worker tags to output so it can all go to one file
50
50
 
51
51
  Beyond
52
52
 
data/bin/sweatshop CHANGED
@@ -7,7 +7,7 @@ require 'robot_sweatshop/config'
7
7
  require 'robot_sweatshop/create-config-directories'
8
8
 
9
9
  program :name, 'Robot Sweatshop'
10
- program :version, '0.4.6'
10
+ program :version, '0.4.7'
11
11
  program :description, 'A lightweight, nonopinionated CI server'
12
12
  program :help, 'Author', 'Justin Scott <jvscott@gmail.com>'
13
13
 
@@ -16,8 +16,8 @@ command :job do |c|
16
16
  c.description = 'Creates and edits jobs.'
17
17
  c.option '--auto', 'Create the file without opening the editor.'
18
18
  c.action do |args, options|
19
- options.default :auto => false
20
- raise "Specify a job name as the command argument." if args.count < 1
19
+ options.default auto: false
20
+ fail 'Specify a job name as the command argument.' if args.count < 1
21
21
  job_file = CLI::Job.path_for args.first
22
22
  CLI.create job_file, with_contents: CLI::Job.default
23
23
  CLI.edit job_file unless options.auto
@@ -27,7 +27,7 @@ end
27
27
  command :'job-list' do |c|
28
28
  c.syntax = 'sweatshop job-list'
29
29
  c.description = 'Lists available job configurations.'
30
- c.action do |args, options|
30
+ c.action do
31
31
  CLI::Job.list
32
32
  end
33
33
  end
@@ -37,7 +37,7 @@ command :config do |c|
37
37
  c.description = 'Creates and edits the user configuration file.'
38
38
  c.option '--auto', 'Create the file without opening the editor.'
39
39
  c.action do |args, options|
40
- options.default :auto => false
40
+ options.default auto: false
41
41
  for_scope = args.first || 'local'
42
42
  config_file = CLI::Config.path for_scope
43
43
  CLI.create config_file, with_contents: CLI::Config.default
@@ -50,7 +50,7 @@ command :start do |c|
50
50
  c.description = 'Start the Sweatshop.'
51
51
  c.option '--testing', 'Load the testing Eye configuration.'
52
52
  c.action do |_args, options|
53
- options.default :testing => false
53
+ options.default testing: false
54
54
  environment = options.testing ? 'testing' : 'production'
55
55
  CLI::Start.sweatshop for_environment: environment
56
56
  end
@@ -12,8 +12,8 @@ using ExtendedEZMQ
12
12
  Contract Hash => Hash
13
13
  def sanitize(data)
14
14
  return {} if data.empty?
15
- data = data.map { |key, value| {key => value.to_s} }
16
- data.reduce(:merge)
15
+ data = data.map { |key, value| { key => value.to_s } }
16
+ data.reduce :merge
17
17
  end
18
18
 
19
19
  Contract Hash => String
@@ -40,11 +40,11 @@ def assemble(job, payload, definition)
40
40
  }
41
41
  end
42
42
 
43
- Contract None => Or[Hash,nil]
43
+ Contract None => Maybe[Hash]
44
44
  def request_job
45
- job_id = @sockets[:conveyor].request({method: 'dequeue'}, {})
45
+ job_id = @sockets[:conveyor].request method: 'dequeue'
46
46
  return nil if job_id.nil?
47
- raw_job = @sockets[:conveyor].request({method: 'lookup', data: job_id}, {})
47
+ raw_job = @sockets[:conveyor].request method: 'lookup', data: job_id
48
48
  puts "Assembling: '#{raw_job}'"
49
49
  raw_job.merge job_id: job_id
50
50
  end
@@ -52,7 +52,7 @@ end
52
52
  Contract EZMQ::Socket, Any => Hash
53
53
  def request(socket, data)
54
54
  response = socket.request data, {}
55
- raise response[:error] unless response[:error].empty?
55
+ fail response[:error] unless response[:error].empty?
56
56
  response[:data]
57
57
  end
58
58
 
@@ -63,19 +63,20 @@ def request_data_for(job)
63
63
  [payload, definition]
64
64
  end
65
65
 
66
- Contract Or[Array,nil], Or[String, nil] => nil
66
+ Contract Maybe[Array], Maybe[String] => nil
67
67
  def check_whitelist(whitelist, branch)
68
68
  return if whitelist.nil?
69
- raise 'Branch not whitelisted' unless whitelist.include? branch
69
+ fail 'Branch not whitelisted' unless whitelist.include? branch
70
70
  end
71
71
 
72
72
  Contract Fixnum => Bool
73
73
  def finish(job_id)
74
- @sockets[:conveyor].request({method: 'finish', data: job_id}, {})
74
+ @sockets[:conveyor].request method: 'finish', data: job_id
75
75
  end
76
76
 
77
77
  job_search = Fiber.new do
78
- # TODO: profiler to get a better idea of how long we should wait based on historical information
78
+ # TODO: profiler to get a better idea of how long we should
79
+ # wait based on historical information
79
80
  timer = ExponentialBackoff.new 0.1, 3
80
81
  loop do
81
82
  sleep timer.next_interval
@@ -101,7 +102,7 @@ loop do
101
102
  payload, definition = request_data_for job
102
103
  check_whitelist definition['branch_whitelist'], payload['branch']
103
104
  assembled_job = assemble job, payload, definition
104
- @sockets[:worker].send(assembled_job, {})
105
+ @sockets[:worker].send assembled_job
105
106
  rescue RuntimeError => error
106
107
  puts error.message
107
108
  finish job[:job_id]
@@ -21,9 +21,9 @@ def enqueue(item)
21
21
  @items.enqueue item
22
22
  end
23
23
 
24
- Contract None => Or[Fixnum,nil]
24
+ Contract None => Maybe[Fixnum]
25
25
  def dequeue
26
- puts "dequeue"
26
+ puts 'dequeue'
27
27
  @items.dequeue
28
28
  end
29
29
 
@@ -46,7 +46,6 @@ end
46
46
 
47
47
  Contract Hash => Any
48
48
  def complete(request)
49
- arguments = []
50
49
  return send(request[:method], request[:data]) if request[:data]
51
50
  send(request[:method])
52
51
  end
@@ -16,12 +16,12 @@ end
16
16
  Contract String => Or[Hash, nil]
17
17
  def load_if_exists(config_path)
18
18
  puts "Reading job configuration from #{config_path}"
19
- YAML.load_file config_path if File.exists? config_path
19
+ YAML.load_file config_path if File.exist? config_path
20
20
  end
21
21
 
22
22
  Contract None => Hash
23
23
  def empty_config
24
- puts "Job configuration not found or empty"
24
+ puts 'Job configuration not found or empty'
25
25
  {}
26
26
  end
27
27
 
@@ -31,8 +31,8 @@ def load_config_for(job_name)
31
31
  end
32
32
 
33
33
  Contract Hash, Hash => Hash
34
- def formatted(payload={}, error:'')
35
- {data: payload, error: error}
34
+ def formatted(payload = {}, error: '')
35
+ { data: payload, error: error }
36
36
  end
37
37
 
38
38
  Contract String => Hash
@@ -11,12 +11,10 @@ using ExtendedEZMQ
11
11
 
12
12
  Contract String => Bool
13
13
  def json?(string)
14
- begin
15
- Oj.load string
16
- true
17
- rescue Oj::ParseError => e
18
- false
19
- end
14
+ Oj.load string
15
+ true
16
+ rescue Oj::ParseError
17
+ false
20
18
  end
21
19
 
22
20
  Contract String => Bool
@@ -40,16 +38,14 @@ end
40
38
 
41
39
  Contract String, String => Or[Hash, nil]
42
40
  def payload_hash_from(payload, format)
43
- begin
44
- Object.const_get("#{format}Payload").new(payload).to_hash
45
- rescue NameError
46
- nil
47
- end
41
+ Object.const_get("#{format}Payload").new(payload).to_hash
42
+ rescue NameError
43
+ nil
48
44
  end
49
45
 
50
46
  Contract Hash, Hash => Hash
51
- def formatted(payload={}, error:'')
52
- {data: payload, error: error}
47
+ def formatted(payload = {}, error:'')
48
+ { data: payload, error: error }
53
49
  end
54
50
 
55
51
  Contract String, String => Hash
data/bin/sweatshop-worker CHANGED
@@ -30,10 +30,8 @@ end
30
30
  Contract Hash, String => Maybe[IO]
31
31
  def stream_command(context, command)
32
32
  command = ensure_shell_for command
33
- IO.popen(context, command) do |io_stream|
34
- while line = io_stream.gets
35
- puts line
36
- end
33
+ IO.popen(context, command) do |stream|
34
+ puts stream.gets until stream.eof? # TODO: stdout not being tested properly
37
35
  end
38
36
  end
39
37
 
@@ -50,7 +48,7 @@ end
50
48
 
51
49
  Contract Fixnum => nil
52
50
  def finish(id)
53
- @sockets[:conveyor].request({method: 'finish', data: id}, {})
51
+ @sockets[:conveyor].request({ method: 'finish', data: id }, {})
54
52
  puts "Job finished.\n\n"
55
53
  end
56
54
 
data/docs/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Process Architecture
2
+
3
+ ![Robot Sweatshop Processes](architecture.gif)
4
+
5
+ # Message Data Schema
6
+
7
+ ## input
8
+
9
+ ```
10
+ PUSH {
11
+ payload Hash
12
+ user_agent String
13
+ job_name String
14
+ }
15
+ ```
16
+
17
+ ## conveyor
18
+
19
+ ```
20
+ REQ {
21
+ method String
22
+ data String
23
+ }
24
+ ```
25
+
26
+ method | parameters | reply
27
+ -------|------------|-------
28
+ enqueue|item |id Fixnum
29
+ dequeue|- |id Fixnum
30
+ lookup |id |item Hash
31
+ finish |id |true Bool
32
+
33
+ ## payload-parser
34
+
35
+ ```
36
+ REQ {
37
+ payload String
38
+ user_agent String
39
+ }
40
+
41
+ REP {
42
+ payload Hash
43
+ }
44
+ ```
45
+
46
+ ## job-dictionary
47
+
48
+ ```
49
+ REQ {
50
+ job_name String
51
+ }
52
+
53
+ REP {
54
+ definition Hash
55
+ }
56
+ ```
57
+
58
+ ## assembler
59
+
60
+ ```
61
+ PUSH {
62
+ context Hash
63
+ commands Array
64
+ job_name String
65
+ job_id Fixnum
66
+ }
67
+ ```
68
+
69
+ # Notes
70
+
71
+ Context is passed around with string keys because it's user provided. Everything else is passed with symbol keys
@@ -0,0 +1,22 @@
1
+ digraph architecture {
2
+ label="Robot Sweatshop Processes"
3
+ labelloc="top"
4
+
5
+ I [label="Input"]
6
+ A [label="Assembler"]
7
+ W [label="Worker"]
8
+ subgraph cluster_0 {
9
+ label="Req/Rep Services"
10
+ labelloc="bottom"
11
+ style="dashed"
12
+ C [label="Conveyor"]
13
+ P [label="Payload Parser"]
14
+ D [label="Job Dictionary"]
15
+ }
16
+
17
+ I->C [taillabel="1..*"]
18
+ A->C [dir="both"]
19
+ A->P [dir="both"]
20
+ A->D [dir="both"]
21
+ A->W [headlabel="1..*",labeldistance=2]
22
+ }
Binary file
@@ -2,6 +2,7 @@ require 'fileutils'
2
2
  require 'robot_sweatshop/config'
3
3
  require 'terminal-announce'
4
4
 
5
+ # Methods common to every CLI command
5
6
  module CLI
6
7
  def self.edit(file)
7
8
  editor = ENV['EDITOR']
@@ -9,8 +10,8 @@ module CLI
9
10
  Announce.info "Manually editing file '#{file}'"
10
11
  system editor, file
11
12
  else
12
- Announce.warning "No editor specified in $EDITOR environment variable"
13
- Announce.info "Displaying file contents instead"
13
+ Announce.warning 'No editor specified in $EDITOR environment variable'
14
+ Announce.info 'Displaying file contents instead'
14
15
  system 'cat', file
15
16
  end
16
17
  end
@@ -18,7 +19,7 @@ module CLI
18
19
  def self.create(file, with_contents: '')
19
20
  file = File.expand_path file
20
21
  if File.file?(file)
21
- Announce.info "'#{file}' already exists"
22
+ Announce.info "'#{file}' already exists"
22
23
  else
23
24
  FileUtils.mkdir_p File.dirname(file)
24
25
  File.write file, with_contents
@@ -1,6 +1,8 @@
1
+ require 'fileutils'
1
2
  require 'robot_sweatshop/config'
2
3
 
3
4
  module CLI
5
+ # Methods for configuring Robot Sweatshop
4
6
  module Config
5
7
  def self.default
6
8
  File.read "#{__dir__}/../../../config.defaults.yaml"
@@ -9,12 +11,30 @@ module CLI
9
11
  def self.path(scope)
10
12
  case scope
11
13
  when 'system'
12
- "/etc/robot_sweatshop/config.yaml"
14
+ '/etc/robot_sweatshop/config.yaml'
13
15
  when 'user'
14
- "~/.robot_sweatshop/config.yaml"
16
+ '~/.robot_sweatshop/config.yaml'
15
17
  else
16
- ".robot_sweatshop/config.yaml"
18
+ '.robot_sweatshop/config.yaml'
17
19
  end
18
20
  end
21
+
22
+ def self.expand_paths_in(config_hash)
23
+ config_hash.each do |key, value|
24
+ config_hash[key] = File.expand_path value if key.to_s.match(/_path/)
25
+ end
26
+ end
27
+
28
+ def self.write(config_hash)
29
+ config_home = File.expand_path('~/.robot_sweatshop')
30
+ FileUtils.mkpath config_home
31
+ File.write "#{config_home}/compiled_config.yaml", config_hash.to_yaml
32
+ end
33
+
34
+ def self.compile_to_file
35
+ config = expand_paths_in configatron.to_h
36
+ config[:working_path] = Dir.pwd
37
+ write config
38
+ end
19
39
  end
20
40
  end
@@ -1,9 +1,19 @@
1
1
  require 'robot_sweatshop/config'
2
2
 
3
3
  module CLI
4
+ # Methods for creating and editing jobs
4
5
  module Job
5
6
  def self.default
6
- "---\n# branch_whitelist:\n# - master\n\ncommands:\n- echo \"Hello $WORLD!\"\n\nenvironment:\n WORLD: Earth\n"
7
+ "---
8
+ # branch_whitelist:
9
+ # - master
10
+
11
+ commands:
12
+ - echo \"Hello $WORLD!\"
13
+
14
+ environment:
15
+ WORLD: Earth
16
+ "
7
17
  end
8
18
 
9
19
  def self.path_for(job)
@@ -3,21 +3,13 @@ require 'terminal-announce'
3
3
  require 'robot_sweatshop/config'
4
4
 
5
5
  module CLI
6
+ # Methods for starting Robot Sweatshop
6
7
  module Start
7
- def self.store_config_for_eye
8
- config = configatron.to_h
9
- config = config.each do |key, value|
10
- config[key] = File.expand_path value if key.to_s.match /_path/
11
- end
12
- config[:working_path] = Dir.pwd
13
- File.write '/tmp/.robot_sweatshop-eye-config.yaml', config.to_yaml
14
- end
15
-
16
8
  def self.sweatshop(for_environment:)
17
- store_config_for_eye
9
+ Config.compile_to_file
18
10
  eye_config = File.expand_path "#{__dir__}/../../../robot_sweatshop.eye"
19
11
  output = `eye load #{eye_config}`
20
- raise output if $?.exitstatus != 0
12
+ fail output if $?.exitstatus != 0
21
13
  Announce.success "Robot Sweatshop loaded with a #{for_environment} configuration"
22
14
  Announce.info `eye restart robot_sweatshop`
23
15
  Announce.info 'Run \'eye --help\' for more info on debugging'
@@ -5,9 +5,9 @@ configatron.reset!
5
5
 
6
6
  configurations = [
7
7
  "#{__dir__}/../../config.defaults.yaml",
8
- "/etc/robot_sweatshop/config.yaml",
9
- "~/.robot_sweatshop/config.yaml",
10
- ".robot_sweatshop/config.yaml",
8
+ '/etc/robot_sweatshop/config.yaml',
9
+ '~/.robot_sweatshop/config.yaml',
10
+ '.robot_sweatshop/config.yaml'
11
11
  ]
12
12
 
13
13
  configurations.each do |config_path|
@@ -2,6 +2,7 @@ require 'ezmq'
2
2
  require 'oj'
3
3
  require 'robot_sweatshop/config'
4
4
 
5
+ # Add some common methods to ZMQ sockets that get repeated everywhere
5
6
  module ExtendedEZMQ
6
7
  refine EZMQ::Socket do
7
8
  def serialize_with_json!
@@ -10,8 +11,8 @@ module ExtendedEZMQ
10
11
  end
11
12
 
12
13
  def close
13
- self.socket.close
14
- self.context.terminate
14
+ socket.close
15
+ context.terminate
15
16
  end
16
17
  end
17
18
  end
@@ -1,19 +1,12 @@
1
1
  require 'fileutils'
2
2
 
3
3
  def create_path(path)
4
- begin
5
- FileUtils.mkdir_p path
6
- rescue Errno::EACCES
7
- puts "Permission denied to create '#{path}'"
8
- end
4
+ FileUtils.mkdir_p path
5
+ rescue Errno::EACCES
6
+ puts "Permission denied to create '#{path}'"
9
7
  end
10
8
 
11
9
  config = configatron.to_h
12
10
  config.each do |key, value|
13
- if key.to_s.match /_path/
14
- create_path File.expand_path(value)
15
- end
16
- end
17
- %w(pidfile_path logfile_path).each do |path|
18
- create_path File.expand_path("#{configatron[path]}/gears")
11
+ create_path File.expand_path(value) if key.to_s.match(/_path/)
19
12
  end
data/robot_sweatshop.eye CHANGED
@@ -1,7 +1,8 @@
1
1
  require 'yaml'
2
2
 
3
3
  # TODO: per-user temp file
4
- CONFIG = YAML.load_file '/tmp/.robot_sweatshop-eye-config.yaml'
4
+ CONFIG_PATH = File.expand_path '~/.robot_sweatshop/compiled_config.yaml'
5
+ CONFIG = YAML.load_file CONFIG_PATH
5
6
  PID_PATH = CONFIG[:pidfile_path]
6
7
  LOG_PATH = CONFIG[:logfile_path]
7
8
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'robot_sweatshop'
3
- gem.version = '0.4.6'
3
+ gem.version = '0.4.7'
4
4
  gem.licenses = 'MIT'
5
5
  gem.authors = ['Justin Scott']
6
6
  gem.email = 'jvscott@gmail.com'
@@ -1,5 +1,5 @@
1
1
  require_relative 'helpers/input'
2
2
  require_relative 'helpers/output'
3
3
 
4
- $a_moment = 0.1
4
+ $a_moment = 0.2
5
5
  $a_while = 1
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.4.6
4
+ version: 0.4.7
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-06-05 00:00:00.000000000 Z
11
+ date: 2015-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -276,7 +276,9 @@ files:
276
276
  - bin/sweatshop-payload-parser
277
277
  - bin/sweatshop-worker
278
278
  - config.defaults.yaml
279
- - lib/README.md
279
+ - docs/README.md
280
+ - docs/architecture.dot
281
+ - docs/architecture.gif
280
282
  - lib/robot_sweatshop.rb
281
283
  - lib/robot_sweatshop/cli.rb
282
284
  - lib/robot_sweatshop/cli/common.rb
data/lib/README.md DELETED
@@ -1,45 +0,0 @@
1
- # Job Lifecycle
2
-
3
- ```
4
- input -> conveyor
5
- { payload:, user_agent:, job_name: }
6
-
7
- assembler <-> conveyor
8
- { method:, data: } <-> data
9
-
10
- assembler <-> payload-parser
11
- { payload:, user_agent: } <-> { payload:, error: }
12
-
13
- assembler <-> job-dictionary
14
- { job_name: } <-> { payload:, error: }
15
-
16
- assembler -> worker
17
- { context:, commands:, job_name:, job_id }
18
- ```
19
-
20
- context is passed around with string keys because it's user provided
21
- everything else is passed with symbol keys
22
-
23
- # Services
24
-
25
- sweatshop-conveyor
26
- ```
27
- { method:, data: }
28
-
29
- id = enqueue(item)
30
- id = dequeue
31
- item = lookup(id)
32
- something = finish(id)
33
- ```
34
-
35
- sweatshop-payload-parser
36
- ```
37
- req: { payload:, user_agent: }
38
- rep: { data:, error: }
39
- ```
40
-
41
- sweatshop-job-dictionary
42
- ```
43
- req: { job_name: }
44
- rep: { data:, error: }
45
- ```