cyclid 0.2.5 → 0.3.0

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