robot_sweatshop 0.4.6 → 0.4.7

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 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
- ```