cyclid 0.2.5 → 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: 075e82b45f23bff6cb7e99792eb97e3d7a55e912
4
- data.tar.gz: 2ccbb21e53f9db060786e22d641a05203c18d420
3
+ metadata.gz: e7112455a4780d7e09049b9a7e248d41e70e4558
4
+ data.tar.gz: 5e7423b5c082b494a05d98ce46d87f1a72540111
5
5
  SHA512:
6
- metadata.gz: e3bcc3a37f8c1bd4b83d90ed61af1bee3482d94b05b60c0966bb05f7fa433a618671d4450ab94890ffeb4fa5bd5b0c672af133067aae5dc68d3d7d0f01893501
7
- data.tar.gz: 35cfcb20a3434bd9e0ff492c00017e64288b51cda48662417b61807e7cf4584f0cba83201592ad648f48a259c71776b55bbc3aff432f8321170dff0e134e4e7f
6
+ metadata.gz: 48fedf1d7150d25fa45787d1e47dc07568488f92151d3237440ecc69d6f71586edc4687eaad16d706758295905d4bda58b860e72422821e0df6462a994f40f92
7
+ data.tar.gz: a95452bf58313dec14d44e8c9c277c9e42c7d0222dd7ed453bd0f730121b2ee5fdf994c827811a4e2c731dc5e05aae84594466b029b523613e4012bf289e2d49
@@ -23,9 +23,9 @@ module Cyclid
23
23
  class UserController < ControllerBase
24
24
  helpers do
25
25
  # Remove sensitive data from the users data
26
- def sanitize_user(user)
26
+ def sanitize_user(user, keys = %w(password secret))
27
27
  user.delete_if do |key, _value|
28
- key == 'password' || key == 'secret'
28
+ keys.include? key
29
29
  end
30
30
  end
31
31
  end
@@ -78,7 +78,8 @@ module Cyclid
78
78
  user_hash = user.serializable_hash
79
79
  user_hash['organizations'] = user.organizations.map(&:name)
80
80
 
81
- user_hash = sanitize_user(user_hash)
81
+ # DO provide the users HMAC secret, in this instance
82
+ user_hash = sanitize_user(user_hash, ['password'])
82
83
 
83
84
  return user_hash.to_json
84
85
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2017 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Top level module for the core Cyclid code.
17
+ module Cyclid
18
+ # Module for the Cyclid API
19
+ module API
20
+ # Module for Cyclid Job related classes
21
+ module Job
22
+ # Evaluator exception class
23
+ class EvalException < RuntimeError
24
+ end
25
+
26
+ # Evalute an expression for "only_if" & "not_if"
27
+ class Evaluator
28
+ class << self
29
+ def only_if(statement, vars)
30
+ evaluate(statement, vars)
31
+ end
32
+
33
+ def not_if(statement, vars)
34
+ not evaluate(statement, vars) # rubocop:disable Style/Not
35
+ end
36
+
37
+ private
38
+
39
+ def compare(lvalue, operator, rvalue)
40
+ case operator
41
+ # Loose (case insensitive) comparision
42
+ when '=='
43
+ lvalue.downcase == rvalue.downcase # rubocop:disable Performance/Casecmp
44
+ # Case sensitive/value comparision
45
+ when '===', 'eq'
46
+ lvalue == rvalue
47
+ # Not-equal
48
+ when '!=', 'ne'
49
+ lvalue != rvalue
50
+ # Less than
51
+ when '<', 'lt'
52
+ lvalue.to_f < rvalue.to_f
53
+ # Greater than
54
+ when '>', 'gt'
55
+ lvalue.to_f > rvalue.to_f
56
+ # Less than or equal
57
+ when '<=', 'le'
58
+ lvalue.to_f <= rvalue.to_f
59
+ # Greater than or equal
60
+ when '>=', 'ge'
61
+ lvalue.to_f >= rvalue.to_f
62
+ # Not an operator we know
63
+ else
64
+ raise EvalException, "unknown operator: #{operator}"
65
+ end
66
+ end
67
+
68
+ def evaluate(statement, vars)
69
+ # Replace single % characters with escaped versions and interpolate
70
+ expr = statement.gsub(/%([^{])/, '%%\1') ** vars
71
+
72
+ # Evaluate for both string comparisons:
73
+ #
74
+ # 'string1' == 'string2'
75
+ #
76
+ # and numbers:
77
+ #
78
+ # 1 < 2
79
+ #
80
+ # Numbers can be integers, floats or percentages
81
+ #
82
+ # Only the ==, != and === operators are recognised for strings. All
83
+ # operators, including == & != are valid for Numbers: === is not a valid
84
+ # operator for numbers.
85
+
86
+ # rubocop:disable Metrics/LineLength
87
+ case expr
88
+ when /\A'(.*)' (==|!=|===) '(.*)'\Z/,
89
+ /\A([0-9]*|[0-9]*\.[0-9]*)%? (==|eq|!=|ne|<|lt|>|gt|<=|le|>=|ge) ([0-9]*|[0-9]*\.[0-9]*)%?\Z/
90
+ compare(Regexp.last_match[1],
91
+ Regexp.last_match[2],
92
+ Regexp.last_match[3])
93
+ else
94
+ raise EvalException, "unable to evaluate #{expr}"
95
+ end
96
+ # rubocop:enable Metrics/LineLength
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -134,14 +134,27 @@ module Cyclid
134
134
  job_sequence << stage_success \
135
135
  unless job_stage[:on_success].nil? or \
136
136
  stage?(job_sequence, job_stage[:on_success])
137
- stage_view.on_success = job_stage[:on_success]
138
137
 
138
+ # Set the on_success handler; if no explicit hander is defined, use
139
+ # the next stage in the sequence
140
+ success_stage = if job_stage[:on_success]
141
+ job_stage[:on_success]
142
+ else
143
+ next_stage(job_sequence, job_stage)
144
+ end
145
+ stage_view.on_success = success_stage
146
+
147
+ # Now set the on_failure failure
139
148
  stage_failure = { stage: job_stage[:on_failure] }
140
149
  job_sequence << stage_failure \
141
150
  unless job_stage[:on_failure].nil? or \
142
151
  stage?(job_sequence, job_stage[:on_failure])
143
152
  stage_view.on_failure = job_stage[:on_failure]
144
153
 
154
+ # Merge in any modifiers
155
+ stage_view.only_if = job_stage[:only_if]
156
+ stage_view.not_if = job_stage[:not_if]
157
+
145
158
  # Store the modified StageView
146
159
  stages[stage_view.name.to_sym] = stage_view
147
160
  end
@@ -158,6 +171,14 @@ module Cyclid
158
171
  end
159
172
  return found
160
173
  end
174
+
175
+ # Get the directly proceeding stage in the sequence
176
+ def next_stage(sequence, stage)
177
+ idx = sequence.index stage
178
+
179
+ next_stage = sequence.at(idx + 1)
180
+ next_stage.nil? ? nil : next_stage['stage']
181
+ end
161
182
  end
162
183
  end
163
184
  end
@@ -124,13 +124,37 @@ module Cyclid
124
124
  stage_definition = stages[sequence.to_sym]
125
125
  stage = Oj.load(stage_definition, symbol_keys: true)
126
126
 
127
- @notifier.write "#{'-' * 79}\n#{Time.now} : " \
128
- "Running stage #{stage.name} v#{stage.version}\n"
127
+ # Evaluate any only_if/not_if expressions. Always run the stage if there are no
128
+ # modifiers.
129
+ do_run = if stage.only_if
130
+ Evaluator.only_if(stage.only_if, @ctx)
131
+ elsif stage.not_if
132
+ Evaluator.not_if(stage.not_if, @ctx)
133
+ else
134
+ true
135
+ end
136
+
137
+ if do_run
138
+ @notifier.write "#{'-' * 79}\n#{Time.now} : " \
139
+ "Running stage #{stage.name} v#{stage.version}\n"
140
+
141
+ # Run the stage
142
+ success, rc = run_stage(stage)
143
+
144
+ Cyclid.logger.info "stage #{(success ? 'succeeded' : 'failed')} and returned #{rc}"
145
+ else
146
+ @notifier.write "#{'-' * 79}\n#{Time.now} : " \
147
+ "Skipping stage #{stage.name} v#{stage.version}\n"
129
148
 
130
- # Run the stage
131
- success, rc = run_stage(stage)
149
+ # Skip this stage; assume success
150
+ success = true
151
+ rc = 0
132
152
 
133
- Cyclid.logger.info "stage #{(success ? 'succeeded' : 'failed')} and returned #{rc}"
153
+ # rubocop:disable Style/MultilineTernaryOperator
154
+ Cyclid.logger.info "stage skipped due to #{stage.only_if ? \
155
+ "only_if #{stage.only_if}" : "not_if #{stage.not_if}"}"
156
+ # rubocop:enable Style/MultilineTernaryOperator
157
+ end
134
158
 
135
159
  # Decide which stage to run next depending on the outcome of this
136
160
  # one
@@ -25,7 +25,7 @@ module Cyclid
25
25
  # the database object.
26
26
  class StageView
27
27
  attr_reader :name, :version, :steps
28
- attr_accessor :on_success, :on_failure
28
+ attr_accessor :on_success, :on_failure, :only_if, :not_if
29
29
 
30
30
  def initialize(arg)
31
31
  if arg.is_a? Cyclid::API::Stage
@@ -9,10 +9,10 @@ class Hash
9
9
  end
10
10
 
11
11
  # Interpolate the data in the ctx hash into any String values
12
- def interpolate(ctx)
12
+ def %(other)
13
13
  hmap do |key, value|
14
14
  if value.is_a? String
15
- { key => value % ctx }
15
+ { key => value ** other }
16
16
  else
17
17
  { key => value }
18
18
  end
@@ -23,15 +23,42 @@ end
23
23
  # Add a method to Array
24
24
  class Array
25
25
  # Interpolate the data in the ctx hash for each String & Hash item
26
- def interpolate(ctx)
26
+ def %(other)
27
27
  map do |entry|
28
28
  if entry.is_a? Hash
29
- entry.interpolate ctx
29
+ entry % other
30
30
  elsif entry.is_a? String
31
- entry % @ctx
31
+ entry ** other
32
32
  else
33
33
  entry
34
34
  end
35
35
  end
36
36
  end
37
37
  end
38
+
39
+ # Add a method to String
40
+ class String
41
+ # Provide a "safe" version of the % (interpolation) operator; if a key does
42
+ # not exist in arg, catch the KeyError and insert it as a nil value, and
43
+ # continue.
44
+ #
45
+ # We're using the ** operator as it's one that isn't already used by String,
46
+ # and it's abusing % already so hey, why not. FTP
47
+ def **(other)
48
+ res = nil
49
+ arg = other ? other.dup : {}
50
+
51
+ begin
52
+ res = self % arg
53
+ rescue KeyError => ex
54
+ # Extract the key name from the exception message (sigh)
55
+ match = ex.message.match(/\Akey{(.*)} not found\Z/)
56
+ key = match[1]
57
+
58
+ # Inject key with a default value and try again
59
+ arg[key.to_sym] = nil
60
+ end while res.nil? # rubocop:disable Lint/Loop
61
+
62
+ return res
63
+ end
64
+ end
@@ -55,7 +55,7 @@ module Cyclid
55
55
  def perform(log)
56
56
  begin
57
57
  # Export the environment data to the build host, if necesary
58
- env = @env.interpolate(@ctx) if @env
58
+ env = @env % @ctx if @env
59
59
  @transport.export_env(env)
60
60
 
61
61
  # Log the command being run (and the working directory, if one is
@@ -64,11 +64,11 @@ module Cyclid
64
64
  log.write(@path.nil? ? "$ #{cmd_args}\n" : "$ #{@path} : #{cmd_args}\n")
65
65
 
66
66
  # Interpolate any data from the job context
67
- cmd_args = cmd_args % @ctx
67
+ cmd_args = cmd_args ** @ctx
68
68
 
69
69
  # Interpolate the path if one is set
70
70
  path = @path
71
- path = path % @ctx unless path.nil?
71
+ path = path ** @ctx unless path.nil?
72
72
 
73
73
  # Run the command
74
74
  success = @transport.exec(cmd_args, path)
@@ -51,9 +51,9 @@ module Cyclid
51
51
  "as #{email_config[:from]}"
52
52
 
53
53
  # Add the job context
54
- to = @to % @ctx
55
- subject = @subject % @ctx
56
- message = @message % @ctx
54
+ to = @to ** @ctx
55
+ subject = @subject ** @ctx
56
+ message = @message ** @ctx
57
57
 
58
58
  # Create a binding for the text & HTML ERB templates
59
59
  info = { color: @color, title: subject }
@@ -32,7 +32,7 @@ module Cyclid
32
32
 
33
33
  # Write the log message, with the context data interpolated
34
34
  def perform(log)
35
- log.write(@message % @ctx)
35
+ log.write("#{@message ** @ctx}\n")
36
36
  true
37
37
  end
38
38
 
@@ -56,12 +56,12 @@ module Cyclid
56
56
  def perform(log)
57
57
  begin
58
58
  # Export the environment data to the build host, if necesary
59
- env = @env.interpolate(@ctx) if @env
59
+ env = @env % @ctx if @env
60
60
  @transport.export_env(env)
61
61
 
62
62
  # Add context data
63
- script = @script % @ctx
64
- path = @path % @ctx
63
+ script = @script ** @ctx
64
+ path = @path ** @ctx
65
65
 
66
66
  # Create an IO object containing the script and upload it to the
67
67
  # build host
@@ -43,15 +43,15 @@ module Cyclid
43
43
  Cyclid.logger.debug "using plugin config #{plugin_data}"
44
44
  config = plugin_data['config']
45
45
 
46
- subject = @subject % @ctx
46
+ subject = @subject ** @ctx
47
47
 
48
48
  url = @url || config['webhook_url']
49
49
  raise 'no webhook URL given' if url.nil?
50
50
 
51
- url = url % @ctx
51
+ url = url ** @ctx
52
52
  Cyclid.logger.debug "sending notification to #{url}"
53
53
 
54
- message_text = @message % @ctx if @message
54
+ message_text = @message ** @ctx if @message
55
55
 
56
56
  # Create a binding for the template
57
57
  bind = binding
@@ -33,8 +33,8 @@ module Cyclid
33
33
  @pr_head ||= pull_request['head']
34
34
  end
35
35
 
36
- def pr_clone_url
37
- pr_head['repo']['html_url']
36
+ def pr_base
37
+ @pr_base ||= pull_request['base']
38
38
  end
39
39
 
40
40
  def pr_sha
@@ -45,22 +45,29 @@ module Cyclid
45
45
  pr_head['ref']
46
46
  end
47
47
 
48
- def pr_repo
49
- @pr_repo ||= pr_head['repo']
48
+ def pr_base_repo
49
+ @pr_base_repo ||= pr_base['repo']
50
50
  end
51
51
 
52
- def pr_status_url
53
- url = pr_repo['statuses_url']
54
- @pr_status_url ||= url.gsub('{sha}', pr_sha)
52
+ def pr_base_url
53
+ pr_base_repo['html_url']
54
+ end
55
+
56
+ def pr_head_repo
57
+ @pr_head_repo ||= pr_head['repo']
58
+ end
59
+
60
+ def pr_head_url
61
+ pr_head_repo['html_url']
55
62
  end
56
63
 
57
64
  def pr_trees_url
58
- url = pr_repo['trees_url']
65
+ url = pr_head_repo['trees_url']
59
66
  @pr_trees_url ||= url.gsub('{/sha}', "/#{pr_sha}")
60
67
  end
61
68
 
62
69
  def pr_repository
63
- @repo ||= Octokit::Repository.from_url(pr_clone_url)
70
+ @repo ||= Octokit::Repository.from_url(pr_base_url)
64
71
  end
65
72
 
66
73
  def push_head_commit
@@ -131,6 +138,23 @@ module Cyclid
131
138
  state = "#{org.name}:#{org.salt}:#{org.owner_email}"
132
139
  Base64.urlsafe_encode64(Digest::SHA256.digest(state))
133
140
  end
141
+
142
+ # Normalize a Github URL into a Cyclid style source definition
143
+ def normalize(url)
144
+ uri = URI.parse(url)
145
+
146
+ source = {}
147
+ source['url'] = "#{uri.scheme}://#{uri.host}#{uri.path.gsub(/.git$/, '')}"
148
+ source['token'] = uri.user if uri.user
149
+ source['branch'] = uri.fragment if uri.fragment
150
+
151
+ source
152
+ end
153
+
154
+ # Extract the "humanish" part from a Git repository URL
155
+ def humanish(uri)
156
+ uri.path.split('/').last.gsub('.git', '')
157
+ end
134
158
  end
135
159
  end
136
160
  end
@@ -43,7 +43,7 @@ module Cyclid
43
43
 
44
44
  # Get the list of files in the root of the repository in the
45
45
  # Pull Request branch
46
- clone_url = URI(pr_clone_url)
46
+ clone_url = URI(pr_head_url)
47
47
 
48
48
  # Get the authentication key
49
49
  auth_token = find_oauth_token(config, clone_url)
@@ -78,15 +78,19 @@ module Cyclid
78
78
  job_definition = load_job_file(pr_repository, job_sha, job_type)
79
79
 
80
80
  # Insert this repository & branch into the sources
81
+ clone_source = normalize(clone_url.to_s)
82
+ clone_source['type'] = 'git'
83
+ clone_source['branch'] = pr_ref
84
+ clone_source['token'] = auth_token
85
+
86
+ # We need to avoid causing a collision between the PR source
87
+ # (which may be from a forked repo) and any source definitions
88
+ # which may exist in the job file (which may be the "base"
89
+ # repository.
81
90
  #
82
- # XXX Could this cause collisions between the existing sources in
83
- # the job definition? Not entirely sure what the workflow will
84
- # look like.
85
- job_sources = job_definition['sources'] || []
86
- job_sources << { 'type' => 'git',
87
- 'url' => clone_url.to_s,
88
- 'branch' => pr_ref,
89
- 'token' => auth_token }
91
+ # Compare everything and try to match any duplicates, and
92
+ # flatten the sources.
93
+ job_sources = insert_or_update_source(job_definition['sources'] || [], clone_source)
90
94
  job_definition['sources'] = job_sources
91
95
 
92
96
  Cyclid.logger.debug "sources=#{job_definition['sources']}"
@@ -110,10 +114,10 @@ module Cyclid
110
114
  linkback_url = "#{ui_url}/#{organization_name}"
111
115
 
112
116
  # Inject some useful context data
113
- ctx = { gh_event: 'pull_request',
114
- gh_user: pull_request['user']['login'],
115
- gh_ref: pr_ref,
116
- gh_comment: pull_request['body'] }
117
+ ctx = { github_event: 'pull_request',
118
+ github_user: pull_request['user']['login'],
119
+ github_ref: pr_ref,
120
+ github_comment: pull_request['title'] }
117
121
 
118
122
  callback = GithubCallback.new(auth_token, pr_repository, pr_sha, linkback_url)
119
123
  job_from_definition(job_definition, callback, ctx)
@@ -126,6 +130,34 @@ module Cyclid
126
130
 
127
131
  return true
128
132
  end
133
+
134
+ # Either insert (append) the Pull Request head repository, or
135
+ # replace an existing definition; for example if the job contains
136
+ # a source definition for "https://github.com/foo/bar" and the PR
137
+ # head is "https://github.com/baz/bar", replace "foo/bar" with
138
+ # "baz/bar"
139
+ def insert_or_update_source(sources, new_source)
140
+ updated = false
141
+ new_uri = URI.parse(new_source['url'])
142
+
143
+ normalized = sources.map do |source|
144
+ uri = URI.parse(source['url'])
145
+ next unless uri.scheme =~ /\Ahttps?\z/
146
+
147
+ # If the "humanish" components match, use the new definition.
148
+ if humanish(uri) == humanish(new_uri)
149
+ updated = true
150
+ new_source
151
+ else
152
+ source
153
+ end
154
+ end
155
+
156
+ # If we didn't update an existing source definition, insert the new one
157
+ normalized << new unless updated
158
+
159
+ normalized.compact
160
+ end
129
161
  end
130
162
  end
131
163
  end
@@ -97,10 +97,10 @@ module Cyclid
97
97
  linkback_url = "#{ui_url}/#{organization_name}"
98
98
 
99
99
  # Inject some useful context data
100
- ctx = { gh_event: 'push',
101
- gh_user: @payload['sender']['login'],
102
- gh_ref: push_ref,
103
- gh_comment: push_head_commit['message'] }
100
+ ctx = { github_event: 'push',
101
+ github_user: @payload['sender']['login'],
102
+ github_ref: push_ref,
103
+ github_comment: push_head_commit['message'] }
104
104
 
105
105
  callback = GithubCallback.new(auth_token, push_repository, push_sha, linkback_url)
106
106
  job_from_definition(job_definition, callback, ctx)
@@ -26,7 +26,7 @@ module Cyclid
26
26
  transport.export_env('DEBIAN_FRONTEND' => 'noninteractive')
27
27
 
28
28
  # Build hosts may require an update before anything can be installed
29
- success = transport.exec 'apt-get update'
29
+ success = transport.exec 'apt-get update -qq'
30
30
  raise 'failed to update repositories' unless success
31
31
 
32
32
  if env.key? :repos
@@ -44,13 +44,13 @@ module Cyclid
44
44
  end
45
45
 
46
46
  # We must update again to cache the new repositories
47
- success = transport.exec 'apt-get update'
47
+ success = transport.exec 'apt-get update -q'
48
48
  raise 'failed to update repositories' unless success
49
49
  end
50
50
 
51
51
  if env.key? :packages
52
52
  success = transport.exec \
53
- "apt-get install -y #{env[:packages].join(' ')}" \
53
+ "apt-get install -q -y #{env[:packages].join(' ')}" \
54
54
 
55
55
  raise "failed to install packages #{env[:packages].join(' ')}" unless success
56
56
  end
@@ -26,12 +26,12 @@ module Cyclid
26
26
  transport.export_env('DEBIAN_FRONTEND' => 'noninteractive')
27
27
 
28
28
  # Build hosts may require an update before anything can be installed
29
- success = transport.exec 'apt-get update'
29
+ success = transport.exec 'apt-get update -qq'
30
30
  raise 'failed to update repositories' unless success
31
31
 
32
32
  if env.key? :repos
33
- # Ensure apt-get-repository is available
34
- transport.exec 'apt-get install -y software-properties-common'
33
+ # Ensure apt-add-repository is available
34
+ transport.exec 'apt-get install -qq -y software-properties-common'
35
35
 
36
36
  env[:repos].each do |repo|
37
37
  next unless repo.key? :url
@@ -50,13 +50,13 @@ module Cyclid
50
50
  end
51
51
 
52
52
  # We must update again to cache the new repositories
53
- success = transport.exec 'apt-get update'
53
+ success = transport.exec 'apt-get update -q'
54
54
  raise 'failed to update repositories' unless success
55
55
  end
56
56
 
57
57
  if env.key? :packages
58
58
  success = transport.exec \
59
- "apt-get install -y #{env[:packages].join(' ')}" \
59
+ "apt-get install -q -y #{env[:packages].join(' ')}" \
60
60
 
61
61
  raise "failed to install packages #{env[:packages].join(' ')}" unless success
62
62
  end
@@ -32,7 +32,7 @@ module Cyclid
32
32
  unless source.key? :url
33
33
 
34
34
  # Add any context data (which could include secrets)
35
- source = source.interpolate(ctx)
35
+ source = source % ctx
36
36
 
37
37
  url = URI(source[:url])
38
38
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Cyclid
3
3
  module Api
4
- VERSION = '0.2.5'
4
+ VERSION = '0.3.0'
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cyclid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristian Van Der Vliet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-10 00:00:00.000000000 Z
11
+ date: 2017-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -374,6 +374,7 @@ files:
374
374
  - app/cyclid/controllers/users/document.rb
375
375
  - app/cyclid/health_helpers.rb
376
376
  - app/cyclid/job.rb
377
+ - app/cyclid/job/evaluator.rb
377
378
  - app/cyclid/job/helpers.rb
378
379
  - app/cyclid/job/job.rb
379
380
  - app/cyclid/job/runner.rb