cutting_edge 0.0.1 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -2
  3. data/Gemfile.lock +130 -0
  4. data/LICENSE +68 -81
  5. data/Procfile +1 -0
  6. data/README.md +272 -74
  7. data/Rakefile +0 -5
  8. data/bin/cutting_edge +60 -45
  9. data/config.rb +55 -0
  10. data/cutting_edge.gemspec +33 -4
  11. data/heroku.config.rb +20 -0
  12. data/lib/cutting_edge.rb +1 -1
  13. data/lib/cutting_edge/app.rb +95 -19
  14. data/lib/cutting_edge/langs.rb +7 -1
  15. data/lib/cutting_edge/langs/python.rb +5 -1
  16. data/lib/cutting_edge/langs/ruby.rb +5 -1
  17. data/lib/cutting_edge/langs/rust.rb +5 -1
  18. data/lib/cutting_edge/public/images/error.svg +24 -0
  19. data/lib/cutting_edge/public/images/languages/python.svg +1 -0
  20. data/lib/cutting_edge/public/images/languages/ruby.svg +1 -0
  21. data/lib/cutting_edge/public/images/languages/rust.svg +1 -0
  22. data/lib/cutting_edge/public/images/ok.svg +24 -0
  23. data/lib/cutting_edge/public/javascript/clipboard.min.js +7 -0
  24. data/lib/cutting_edge/public/javascript/cuttingedge.js +53 -0
  25. data/lib/cutting_edge/public/stylesheets/primer.css +22 -0
  26. data/lib/cutting_edge/repo.rb +124 -18
  27. data/lib/cutting_edge/templates/_footer.html.erb +3 -0
  28. data/lib/cutting_edge/templates/_header.html.erb +8 -0
  29. data/lib/cutting_edge/templates/_overview.html.erb +9 -0
  30. data/lib/cutting_edge/templates/badge.svg.erb +39 -0
  31. data/lib/cutting_edge/templates/index.html.erb +62 -0
  32. data/lib/cutting_edge/templates/info.html.erb +101 -0
  33. data/lib/cutting_edge/templates/mail.html.erb +163 -0
  34. data/lib/cutting_edge/workers/badge.rb +33 -11
  35. data/lib/cutting_edge/workers/dependency.rb +36 -16
  36. data/lib/cutting_edge/workers/helpers.rb +8 -0
  37. data/lib/cutting_edge/workers/mail.rb +38 -0
  38. data/projects.yml +25 -0
  39. data/spec/app_spec.rb +115 -0
  40. data/spec/badge_worker_spec.rb +77 -0
  41. data/spec/dependency_worker_spec.rb +132 -0
  42. data/spec/email_worker_spec.rb +43 -0
  43. data/spec/fixtures.rb +180 -0
  44. data/spec/fixtures/projects.yml +27 -0
  45. data/spec/langs/python_spec.rb +47 -5
  46. data/spec/langs/ruby_spec.rb +105 -0
  47. data/spec/langs/rust_spec.rb +31 -0
  48. data/spec/repo_spec.rb +52 -0
  49. data/spec/spec_helper.rb +9 -1
  50. metadata +43 -15
  51. data/lib/cutting_edge/badge.rb +0 -46
data/Rakefile CHANGED
@@ -55,11 +55,6 @@ def gemspec_file
55
55
  "#{name}.gemspec"
56
56
  end
57
57
 
58
- desc "Build the gem file."
59
- task :build do
60
- system "gem build gollum_git_adapter_specs.gemspec"
61
- end
62
-
63
58
  def gem_file
64
59
  "#{name}-#{version}.gem"
65
60
  end
@@ -1,72 +1,87 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require File.expand_path('../../lib/cutting_edge/app.rb', __FILE__)
4
3
  require 'yaml'
5
4
  require 'rufus-scheduler'
5
+ require 'optparse'
6
+ require 'semantic_logger'
6
7
 
7
- config = <<YAML
8
- github:
9
- gollum:
10
- gollum:
11
- api_token: secret
12
- gollum-lib:
13
- api_token: secret
14
- locations: [gemspec.rb]
15
- dependency_types: [runtime, development]
16
- singingwolfboy:
17
- flask-dance:
18
- language: python
19
- rust-lang:
20
- crates.io:
21
- language: rust
22
- api_token: secret
23
- gitlab:
24
- cthowl01:
25
- team-chess-ruby:
26
- api_token: secret
27
- YAML
8
+ module CuttingEdge
9
+ REFRESH_SCHEDULE = '1h'
10
+ end
28
11
 
29
12
  options = {
30
13
  :port => 4567,
31
- :bind => '0.0.0.0',
14
+ :bind => '0.0.0.0'
32
15
  }
33
16
 
34
- store = Moneta.new(:Memory)
35
-
36
- repositories = {}
37
- YAML.load(config).each do |source, orgs|
38
- orgs.each do |org, value|
39
- value.each do |name, settings|
40
- cfg = settings.is_a?(Hash) ? settings : {}
41
- repo = Object.const_get("CuttingEdge::#{source.capitalize}Repository").new(org, name, cfg.fetch('language', nil), cfg.fetch('locations', nil), cfg.fetch('branch', nil), cfg.fetch('api_token', nil))
42
- repo.dependency_types = cfg['dependency_types'].map {|dep| dep.to_sym} if cfg['dependency_types'].is_a?(Array)
43
- repositories["#{source}/#{org}/#{name}"] = repo
17
+ opts = OptionParser.new do |opts|
18
+ opts.banner = 'CuttingEdge is a dependency monitoring application.
19
+
20
+ Usage:
21
+ cutting_edge [options] [projects]
22
+
23
+ Arguments:
24
+ [projects] Path to the YAML file which defines the projects to be monitored. If not specified, projects.yml in the current working directory is used.
25
+ '
26
+ opts.separator ''
27
+
28
+ opts.on('-h', '--host [HOST]', 'Specify the hostname or IP address to listen on. Default: \'0.0.0.0\'.') do |host|
29
+ options[:bind] = host
30
+ end
31
+
32
+ opts.on('-p', '--port [PORT]', 'Specify the port to bind to. Default: \'4567\'.') do |port|
33
+ begin
34
+ # don't use 'port.to_i' here... it doesn't raise errors which might result in a nice confusion later on
35
+ options[:port] = Integer(port)
36
+ rescue ArgumentError
37
+ puts "Error: '#{port}' is not a valid port number."
38
+ exit 1
44
39
  end
45
40
  end
41
+
42
+ opts.on('-c', '--config [FILE]', 'Specify path to a .rb configuration file. Default: config.rb') do |file|
43
+ options[:config] = file || 'config.rb'
44
+ end
45
+ end
46
+
47
+ begin
48
+ opts.parse!
49
+ rescue OptionParser::InvalidOption => e
50
+ puts "cutting_edge: #{e.message}"
51
+ puts 'cutting_edge: try \'cutting_edge --help\' for more information'
52
+ exit
53
+ end
54
+
55
+ if cfg = options[:config]
56
+ # If the path begins with a '/' it will be considered an absolute path,
57
+ # otherwise it will be relative to the CWD
58
+ cfg = File.join(Dir.getwd, cfg) unless cfg.slice(0) == File::SEPARATOR
59
+ require cfg
46
60
  end
47
61
 
62
+ # Only require the app after loading the optional config file, to give user the chance to define constants.
63
+ require File.expand_path('../../lib/cutting_edge/app.rb', __FILE__)
64
+
65
+ include CuttingEdgeHelpers
66
+ repositories = load_repositories(ARGV[0] || 'projects.yml')
67
+ repositories.merge!(CuttingEdge::REPOSITORIES) if defined?(CuttingEdge::REPOSITORIES)
68
+
69
+ CuttingEdge::STORE = Moneta.new(:Memory) unless defined?(CuttingEdge::STORE)
70
+ CuttingEdge::App.set(:store, CuttingEdge::STORE)
71
+
48
72
  # Need to initialize the log like this once, because otherwise it only becomes available after the Sinatra app has received a request...
49
73
  ::SemanticLogger.add_appender(file_name: "#{CuttingEdge::App.environment}.log")
50
74
 
51
75
  CuttingEdge::App.set(:repositories, repositories)
52
- CuttingEdge::App.set(:store, store)
53
76
  CuttingEdge::App.set(:enable_logging, true)
54
77
 
55
- puts "Scheduling Jobs..."
78
+ puts 'Scheduling Jobs...'
56
79
  scheduler = Rufus::Scheduler.new
57
- scheduler.every('1h') do
80
+ scheduler.every(CuttingEdge::REFRESH_SCHEDULE) do
58
81
  worker_fetch_all(repositories.values)
59
82
  end
60
- scheduler.every('1h5m') do
61
- worker_all_badges(repositories.values)
62
- end
63
83
 
64
- puts "Running Workers a first time..."
65
- include CuttingEdgeHelpers
84
+ puts 'Running Workers a first time...'
66
85
  worker_fetch_all(repositories.values)
67
86
 
68
- sleep 5
69
- worker_all_badges(repositories.values)
70
-
71
- puts "Starting Sinatra..."
72
87
  CuttingEdge::App.run!(options)
@@ -0,0 +1,55 @@
1
+ # Example config.rb for CuttingEdge
2
+ # All the settings below are purely optional: they all have sane defaults.
3
+
4
+ # Configure mail server settings (outside the CuttingEdge module)
5
+ require 'mail'
6
+ Mail.defaults do
7
+ delivery_method :smtp, address: 'localhost', port: 25
8
+ end
9
+
10
+ module CuttingEdge
11
+ REFRESH_SCHEDULE = '1h' # How often to run workers to check for changes to dependency status. Examples of valid values: '10d', '10m', etc.
12
+ SERVER_HOST = 'mydependencymonitoring.com' # At what domain is this CuttingEdge instance running? Defaults to 'localhost'
13
+ SERVER_URL = "https://#{SERVER_HOST}:4567" # The URL used to refer to this CuttingEdge instance, for instance in e-mails. Defaults to 'http://#{SERVER_HOST}'
14
+
15
+ MAIL_TO = 'mydeps@mymail.com' # Default address to send email to. If set to false (=default!), don't send any e-mails except for repositories that have their 'email:' attribute set in projects.yml
16
+ MAIL_FROM = "cutting_edge@#{SERVER_HOST}" # From Address used for sending e-mails. Default: "cutting_edge@#{SERVER_HOST}"
17
+ MAIL_TEMPLATE = <<EOF
18
+ Define your own ERB template for e-mail updates from CuttingEdge here!
19
+ Of course, you can also File.read it from a separate template file.
20
+ See lib/cutting_edge/templates/mail.html.erb for the default template, and the available variables.
21
+ EOF
22
+
23
+ SECRET_TOKEN = 'mysecrettoken' # Global administrative secret
24
+
25
+ LAST_VERSION_TIMEOUT = 5 # Number of seconds after which to fail when trying to determine the latest version for a dependency.
26
+
27
+ BADGE_COLORS = {
28
+ ok: 'blue',
29
+ outdated_patch: '#dfb317',
30
+ outdated_minor: '#fe7d37',
31
+ outdated_major: '#e05d44',
32
+ unknown: '#9f9f9f'
33
+ } # Redefine the colors used in SVG badges
34
+
35
+ # The following will allow you to define projects on your own GitLab or Gitea server in projects.yml
36
+ # This will allow you to use the 'mygitlab' and 'mygitea' keys, respectively, in projects.yml
37
+ require './lib/cutting_edge/repo.rb'
38
+ define_gitlab_server('mygitlab', 'https://mygitlab.com')
39
+ define_gitea_server('mygitea', 'https://mygitea.com')
40
+ # Now you may use your own server instance in projects.yml like so:
41
+ # mygitlab:
42
+ # orgname:
43
+ # projectname:
44
+ # language: rust
45
+ # ruby_project:
46
+ # language ruby
47
+
48
+ # You may also define additional repositories programatically as follows.
49
+ # These will be added to the repositories defined in projects.yml.
50
+ # This may be useful, for instance, if you want to use environment variables to keep information secret.
51
+ # Note: you need to require './lib/cutting_edge/repo.rb' first
52
+ REPOSITORIES = {
53
+ "gitlab/#{ENV['PRIVATE_REPO1_ORG']}/#{ENV['PRIVATE_REPO1_NAME']}" => GitlabRepository.new(org: ENV['PRIVATE_REPO1_ORG'], name: ENV['PRIVATE_REPO1_NAME'], lang: 'python', auth_token: ENV['PRIVATE_REPO1_TOKEN'], hide: true)
54
+ }
55
+ end
@@ -5,8 +5,8 @@ Gem::Specification.new do |s|
5
5
  s.required_ruby_version = '>= 2.4'
6
6
 
7
7
  s.name = 'cutting_edge'
8
- s.version = '0.0.1'
9
- s.date = '2020-02-19'
8
+ s.version = '0.1'
9
+ s.date = '2020-11-02'
10
10
  s.license = 'GPL-3.0-only'
11
11
 
12
12
  s.summary = 'Self-hosted dependency monitoring, including shiny badges.'
@@ -25,32 +25,61 @@ Gem::Specification.new do |s|
25
25
  s.add_dependency 'sucker_punch', '~> 2.1'
26
26
  s.add_dependency 'sinatra', '~> 2.0'
27
27
  s.add_dependency 'moneta', '~> 1.2'
28
- s.add_dependency 'victor', '~> 0.2.8'
29
28
  s.add_dependency 'rufus-scheduler', '~> 3.6'
30
29
  s.add_dependency 'sinatra-logger', '~> 0.3'
31
30
  s.add_dependency 'toml-rb', '~> 2.0'
31
+ s.add_dependency 'mail', '~> 2.7'
32
+
32
33
  # = MANIFEST =
33
34
  s.files = %w[
34
35
  Gemfile
36
+ Gemfile.lock
35
37
  LICENSE
38
+ Procfile
36
39
  README.md
37
40
  Rakefile
38
41
  bin/cutting_edge
42
+ config.rb
39
43
  cutting_edge.gemspec
44
+ heroku.config.rb
40
45
  lib/cutting_edge.rb
41
46
  lib/cutting_edge/app.rb
42
- lib/cutting_edge/badge.rb
43
47
  lib/cutting_edge/langs.rb
44
48
  lib/cutting_edge/langs/python.rb
45
49
  lib/cutting_edge/langs/ruby.rb
46
50
  lib/cutting_edge/langs/rust.rb
51
+ lib/cutting_edge/public/images/error.svg
52
+ lib/cutting_edge/public/images/languages/python.svg
53
+ lib/cutting_edge/public/images/languages/ruby.svg
54
+ lib/cutting_edge/public/images/languages/rust.svg
55
+ lib/cutting_edge/public/images/ok.svg
56
+ lib/cutting_edge/public/javascript/clipboard.min.js
57
+ lib/cutting_edge/public/javascript/cuttingedge.js
58
+ lib/cutting_edge/public/stylesheets/primer.css
47
59
  lib/cutting_edge/repo.rb
60
+ lib/cutting_edge/templates/_footer.html.erb
61
+ lib/cutting_edge/templates/_header.html.erb
62
+ lib/cutting_edge/templates/_overview.html.erb
63
+ lib/cutting_edge/templates/badge.svg.erb
64
+ lib/cutting_edge/templates/index.html.erb
65
+ lib/cutting_edge/templates/info.html.erb
66
+ lib/cutting_edge/templates/mail.html.erb
48
67
  lib/cutting_edge/versions.rb
49
68
  lib/cutting_edge/workers/badge.rb
50
69
  lib/cutting_edge/workers/dependency.rb
51
70
  lib/cutting_edge/workers/helpers.rb
71
+ lib/cutting_edge/workers/mail.rb
72
+ projects.yml
73
+ spec/app_spec.rb
74
+ spec/badge_worker_spec.rb
75
+ spec/dependency_worker_spec.rb
76
+ spec/email_worker_spec.rb
77
+ spec/fixtures.rb
78
+ spec/fixtures/projects.yml
52
79
  spec/langs/python_spec.rb
80
+ spec/langs/ruby_spec.rb
53
81
  spec/langs/rust_spec.rb
82
+ spec/repo_spec.rb
54
83
  spec/spec_helper.rb
55
84
  ]
56
85
  # = MANIFEST =
@@ -0,0 +1,20 @@
1
+ # Default settings required for Heroku deployment
2
+
3
+ module CuttingEdge
4
+ SERVER_HOST = "#{ENV['HEROKU_APP_NAME']}.herokuapp.com" # At what domain is this CuttingEdge instance running?
5
+ SERVER_URL = "https://#{SERVER_HOST}"
6
+ MAIL_TO = ENV['CUTTING_EDGE_MAIL_TO'] if ENV['CUTTING_EDGE_MAIL_TO']
7
+
8
+ # Your additional configuration goes here.
9
+ # If you are going to host the repository containing this file publically (e.g. on GitHub), please read:
10
+ # https://github.com/repotag/cutting_edge/blob/master/README.md#Defining-repositories-in-configrb
11
+ end
12
+
13
+ # Needed to write to Heroku logs.
14
+ ::SemanticLogger.add_appender(io: $stderr)
15
+
16
+ # Configure mail server settings
17
+ require 'mail'
18
+ Mail.defaults do
19
+ delivery_method :smtp, address: ENV['MAILGUN_SMTP_SERVER'], port: ENV['MAILGUN_SMTP_PORT'], user_name: ENV['MAILGUN_SMTP_LOGIN'], password: ENV['MAILGUN_SMTP_PASSWORD'], domain: CuttingEdge::SERVER_HOST
20
+ end
@@ -1,3 +1,3 @@
1
1
  module CuttingEdge
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1'
3
3
  end
@@ -9,18 +9,10 @@ require File.expand_path('../../cutting_edge.rb', __FILE__)
9
9
  require File.expand_path('../repo.rb', __FILE__)
10
10
  require File.expand_path('../workers/dependency.rb', __FILE__)
11
11
  require File.expand_path('../workers/badge.rb', __FILE__)
12
+ require File.expand_path('../workers/mail.rb', __FILE__)
12
13
 
13
14
  module CuttingEdgeHelpers
14
- def worker_all_badges(repositories)
15
- repositories.each do |repo|
16
- worker_generate_badge(repo)
17
- end
18
- end
19
-
20
- def worker_generate_badge(repo)
21
- BadgeWorker.perform_async(repo.identifier)
22
- end
23
-
15
+
24
16
  def worker_fetch_all(repositories)
25
17
  repositories.each do |repo|
26
18
  worker_fetch(repo)
@@ -28,38 +20,110 @@ module CuttingEdgeHelpers
28
20
  end
29
21
 
30
22
  def worker_fetch(repo)
31
- DependencyWorker.perform_async(repo.identifier, repo.lang, repo.locations, repo.dependency_types)
23
+ DependencyWorker.perform_async(repo.identifier, repo.lang, repo.locations, repo.dependency_types, repo.contact_email, repo.auth_token)
24
+ end
25
+
26
+ def load_repositories(path)
27
+ repositories = {}
28
+ begin
29
+ YAML.load(File.read(path)).each do |source, orgs|
30
+ orgs.each do |org, value|
31
+ value.each do |name, settings|
32
+ cfg = settings.is_a?(Hash) ? settings : {}
33
+ repo = Object.const_get("CuttingEdge::#{source.capitalize}Repository").new(org: org, name: name, lang: cfg.fetch('language', nil), locations: cfg.fetch('locations', nil), branch: cfg.fetch('branch', nil), email: cfg.fetch('email', CuttingEdge::MAIL_TO), auth_token: cfg.fetch('auth_token', nil), hide: cfg.fetch('hide', false))
34
+ repo.dependency_types = cfg['dependency_types'].map {|dep| dep.to_sym} if cfg['dependency_types'].is_a?(Array)
35
+ repositories["#{source}/#{org}/#{name}"] = repo
36
+ end
37
+ end
38
+ end
39
+ rescue SyntaxError, Errno::ENOENT => e
40
+ puts "Error: #{path} does not contain a valid YAML project definition."
41
+ if ENV['RACK_ENV'] == 'test'
42
+ return nil
43
+ else
44
+ exit 1
45
+ end
46
+ end
47
+ repositories
32
48
  end
33
49
  end
34
50
 
35
-
36
51
  module CuttingEdge
37
- LAST_VERSION_TIMEOUT = 5
38
52
 
53
+ LAST_VERSION_TIMEOUT = 5
54
+ SERVER_HOST = 'localhost' unless defined?(SERVER_HOST)
55
+ SERVER_URL = "http://#{SERVER_HOST}" unless defined?(SERVER_URL)
56
+ MAIL_TO = false unless defined?(MAIL_TO) # Default address to send email to. If set to false, don't send any e-mails except for repositories that have their 'email' attribute set.
57
+ MAIL_FROM = "cutting_edge@#{SERVER_HOST}" unless defined?(MAIL_FROM)
58
+
39
59
  class App < Sinatra::Base
40
60
  include CuttingEdgeHelpers
41
61
 
62
+ set :views, ::File.join(::File.dirname(__FILE__), 'templates')
63
+ Tilt.register Tilt::ERBTemplate, 'html.erb'
64
+ set :public_folder, ::File.join(::File.dirname(__FILE__), 'public')
42
65
  logger filename: "#{settings.environment}.log", level: :trace
43
66
 
44
67
  before do
45
68
  @store = settings.store
46
69
  end
47
70
 
48
- get %r{/(.+)/(.+)/(.+)/info} do |source, org, name|
71
+ get '/' do
72
+ hidden_repos, public_repos = CuttingEdge::App.repositories.partition{|_, repo| repo.hidden?}.map(&:to_h)
73
+ @hidden_repos_exist = !hidden_repos.empty?
74
+ @repos = public_repos
75
+ erb :index
76
+ end
77
+
78
+ post '/hidden_repos' do
79
+ payload = JSON.parse(request.body.read)
80
+ if valid_token?(payload['token'])
81
+ @repos = CuttingEdge::App.repositories.select{|_, repo| repo.hidden?}
82
+ partial = Tilt::ERBTemplate.new(::File.join(CuttingEdge::App.views, '_overview.html.erb'))
83
+ {partial: partial.render(self)}.to_json
84
+ else
85
+ status 401
86
+ end
87
+
88
+ end
89
+
90
+ get %r{/(.+)/(.+)/(.+)/info/json} do |source, org, name|
49
91
  repo_defined?(source, org, name)
92
+ validate_repo_token(params[:token]) if @repo.hidden?
50
93
  content_type :json
51
- @store[@repo.identifier].merge({:language => @repo.lang}).to_json # Todo: check whether value exists yet? If not, call worker / wait / timeout?
94
+ data = @store[@repo.identifier]
95
+ if data
96
+ data.merge({:language => @repo.lang}).to_json
97
+ else
98
+ status 500
99
+ end
100
+ end
101
+
102
+ get %r{/(.+)/(.+)/(.+)/info} do |source, org, name|
103
+ repo_defined?(source, org, name)
104
+ validate_repo_token(params[:token]) if @repo.hidden?
105
+ @name = name
106
+ @svg = url("/#{source}/#{org}/#{name}/svg")
107
+ @svg = "#{@svg}?token=#{@repo.hidden_token}" if @repo.hidden?
108
+ @md = "[![Cutting Edge Dependency Status](#{@svg} 'Cutting Edge Dependency Status')](#{url("/#{source}/#{org}/#{name}/info")})"
109
+ @colors = {ok: 'green', outdated_patch: 'yellow', outdated_minor: 'orange', outdated_major: 'red', unknown: 'gray'}
110
+ @specs = @store[@repo.identifier]
111
+ @project_url = @repo.url_for_project
112
+ @language = @repo.lang
113
+ erb :info
52
114
  end
53
115
 
54
116
  get %r{/(.+)/(.+)/(.+)/svg} do |source, org, name|
55
117
  repo_defined?(source, org, name)
118
+ validate_repo_token(params[:token]) if @repo.hidden?
119
+ cache_control no_cache: true
56
120
  content_type 'image/svg+xml'
57
121
  @store["svg-#{@repo.identifier}"]
58
122
  end
59
123
 
60
124
  post %r{/(.+)/(.+)/(.+)/refresh} do |source, org, name|
61
125
  repo_defined?(source, org, name)
62
- if @repo.token && params[:token] == @repo.token
126
+ if valid_token?(params[:token])
63
127
  worker_fetch(@repo)
64
128
  status 200
65
129
  else
@@ -68,10 +132,22 @@ module CuttingEdge
68
132
  end
69
133
 
70
134
  private
71
-
135
+
136
+ def not_found
137
+ halt 404, '404 Not Found'
138
+ end
139
+
140
+ def valid_token?(token)
141
+ defined?(::CuttingEdge::SECRET_TOKEN) && token == ::CuttingEdge::SECRET_TOKEN
142
+ end
143
+
144
+ def validate_repo_token(token)
145
+ not_found unless token == @repo.hidden_token
146
+ end
147
+
72
148
  def repo_defined?(source, org, name)
73
- halt 404, '404 Not Found' unless @repo = settings.repositories["#{source}/#{org}/#{name}"]
149
+ not_found unless @repo = settings.repositories["#{source}/#{org}/#{name}"]
74
150
  end
75
-
151
+
76
152
  end
77
153
  end