cutting_edge 0.0.1
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 +7 -0
- data/Gemfile +10 -0
- data/LICENSE +674 -0
- data/README.md +95 -0
- data/Rakefile +159 -0
- data/bin/cutting_edge +72 -0
- data/cutting_edge.gemspec +57 -0
- data/lib/cutting_edge.rb +3 -0
- data/lib/cutting_edge/app.rb +77 -0
- data/lib/cutting_edge/badge.rb +46 -0
- data/lib/cutting_edge/langs.rb +123 -0
- data/lib/cutting_edge/langs/python.rb +102 -0
- data/lib/cutting_edge/langs/ruby.rb +47 -0
- data/lib/cutting_edge/langs/rust.rb +68 -0
- data/lib/cutting_edge/repo.rb +67 -0
- data/lib/cutting_edge/versions.rb +46 -0
- data/lib/cutting_edge/workers/badge.rb +22 -0
- data/lib/cutting_edge/workers/dependency.rb +100 -0
- data/lib/cutting_edge/workers/helpers.rb +24 -0
- data/spec/langs/python_spec.rb +89 -0
- data/spec/langs/rust_spec.rb +81 -0
- data/spec/spec_helper.rb +111 -0
- metadata +191 -0
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
[](https://travis-ci.org/repotag/cutting_edge)
|
2
|
+
[](https://coveralls.io/github/repotag/cutting_edge?branch=master)
|
3
|
+
|
4
|
+
# CuttingEdge
|
5
|
+
|
6
|
+
## Sinatra App
|
7
|
+
|
8
|
+
* start the app with `bundle exec ruby lib/app.rb`
|
9
|
+
* Runs on http://localhost:4567
|
10
|
+
* Routes under `/source/org/name`
|
11
|
+
* For example: http://localhost:4567/github/gollum/gollum/info
|
12
|
+
|
13
|
+
## DependencyWorker
|
14
|
+
|
15
|
+
The DependencyWorker currently performs the following actions:
|
16
|
+
|
17
|
+
* Fetch gemfile and gemspec for a given Gem
|
18
|
+
* Currently, the `GithubGem` class specifies the needed information for a gem hosted on github, ditto for the `GitlabGem` class for gitlab.
|
19
|
+
* These are subclasses of `RepositoryGem`.
|
20
|
+
* Parse both files for dependency requirements
|
21
|
+
* Does not bork if either of the files could not be retrieved.
|
22
|
+
* Determine the latest version for each required gem
|
23
|
+
* By querying rubygems for a version of the gem `>= 0`.
|
24
|
+
* Generate a Hash of results of the following form:
|
25
|
+
```ruby
|
26
|
+
{:gemspec=>
|
27
|
+
{:outdated_major=>
|
28
|
+
[{:name=>"kramdown",
|
29
|
+
:required=>"~> 1.9.0",
|
30
|
+
:latest=>"2.1.0",
|
31
|
+
:type=>:runtime},
|
32
|
+
{:name=>"sinatra",
|
33
|
+
:required=>"~> 1.4, >= 1.4.4",
|
34
|
+
:latest=>"2.0.8.1",
|
35
|
+
:type=>:runtime},
|
36
|
+
{:name=>"mustache",
|
37
|
+
:required=>">= 0.99.5, < 1.0.0",
|
38
|
+
:latest=>"1.1.1",
|
39
|
+
:type=>:runtime},
|
40
|
+
{:name=>"gemojione",
|
41
|
+
:required=>"~> 3.2",
|
42
|
+
:latest=>"4.3.2",
|
43
|
+
:type=>:runtime}],
|
44
|
+
:outdated_minor=>[],
|
45
|
+
:outdated_bump=>[],
|
46
|
+
:ok=>
|
47
|
+
[{:name=>"gollum-lib",
|
48
|
+
:required=>"~> 4.2, >= 4.2.10",
|
49
|
+
:latest=>"4.2.10",
|
50
|
+
:type=>:runtime},
|
51
|
+
{:name=>"useragent",
|
52
|
+
:required=>"~> 0.16.2",
|
53
|
+
:latest=>"0.16.10",
|
54
|
+
:type=>:runtime}],
|
55
|
+
:unknown=>[]},
|
56
|
+
:gemfile=>
|
57
|
+
{:outdated_major=>
|
58
|
+
[{:name=>"rake",
|
59
|
+
:required=>"~> 10.4",
|
60
|
+
:latest=>"13.0.1",
|
61
|
+
:type=>:runtime}],
|
62
|
+
:outdated_minor=>[],
|
63
|
+
:outdated_bump=>[],
|
64
|
+
:ok=>[],
|
65
|
+
:unknown=>[]}}
|
66
|
+
|
67
|
+
```
|
68
|
+
* These results are stored in a Moneta store under a key generated by `RepositoryGem#identifier` (for e.g. a `GithubGem`, the identifier will be of the form `'github/org/name'`).
|
69
|
+
* The Moneta store used by the Worker is provided by the Sinatra app Class: `::CuttingEdge::App.store`.
|
70
|
+
* So we must do e.g. `CuttingEdge::App.set(:store, Moneta.new(:Memory))` to set the `#store` method on the `CuttingEdge` Class.
|
71
|
+
|
72
|
+
### Example use of the DependencyWorker and Gem API
|
73
|
+
|
74
|
+
See `test.rb` for a basic example (without Sinatra) of how the setup works. Run it with `bundle exec ruby test.rb`.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'redis-objects'
|
78
|
+
|
79
|
+
gem = GithubGem.new('gollum', 'gollum')
|
80
|
+
|
81
|
+
Redis::Objects.redis = Redis.new
|
82
|
+
gem_dependencies = Redis::Value.new(gem.identifier)
|
83
|
+
|
84
|
+
puts gem_dependencies.value # Currently nil
|
85
|
+
|
86
|
+
DependencyWorker.perform_async(gem.identifier, gem.gemspec_location, gem.gemfile_location) # Fire up a Sidekiq job
|
87
|
+
|
88
|
+
# Sleep 5
|
89
|
+
|
90
|
+
puts gem_dependencies.value # The JSON results hash
|
91
|
+
```
|
92
|
+
|
93
|
+
## License
|
94
|
+
|
95
|
+
This work is licensed under the terms of the [GNU GPLv3.0](LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'date'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task :default => :rspec
|
6
|
+
|
7
|
+
desc "Run specs."
|
8
|
+
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
9
|
+
ruby_opts = "-w"
|
10
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
11
|
+
spec.rspec_opts = ['--backtrace --color']
|
12
|
+
end
|
13
|
+
|
14
|
+
#############################################################################
|
15
|
+
#
|
16
|
+
# Helper functions
|
17
|
+
#
|
18
|
+
#############################################################################
|
19
|
+
|
20
|
+
def name
|
21
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
22
|
+
end
|
23
|
+
|
24
|
+
def version
|
25
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
26
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
27
|
+
end
|
28
|
+
|
29
|
+
# assumes x.y.z all digit version
|
30
|
+
def next_version
|
31
|
+
# x.y.z
|
32
|
+
v = version.split '.'
|
33
|
+
# bump z
|
34
|
+
v[-1] = v[-1].to_i + 1
|
35
|
+
v.join '.'
|
36
|
+
end
|
37
|
+
|
38
|
+
def bump_version
|
39
|
+
old_file = File.read("lib/#{name}.rb")
|
40
|
+
old_version_line = old_file[/^\s*VERSION\s*=\s*.*/]
|
41
|
+
new_version = next_version
|
42
|
+
# replace first match of old vesion with new version
|
43
|
+
old_file.sub!(old_version_line, " VERSION = '#{new_version}'")
|
44
|
+
|
45
|
+
File.write("lib/#{name}.rb", old_file)
|
46
|
+
|
47
|
+
new_version
|
48
|
+
end
|
49
|
+
|
50
|
+
def date
|
51
|
+
Date.today.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def gemspec_file
|
55
|
+
"#{name}.gemspec"
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Build the gem file."
|
59
|
+
task :build do
|
60
|
+
system "gem build gollum_git_adapter_specs.gemspec"
|
61
|
+
end
|
62
|
+
|
63
|
+
def gem_file
|
64
|
+
"#{name}-#{version}.gem"
|
65
|
+
end
|
66
|
+
|
67
|
+
def replace_header(head, header_name)
|
68
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
69
|
+
end
|
70
|
+
|
71
|
+
#############################################################################
|
72
|
+
#
|
73
|
+
# Custom tasks (add your own tasks here)
|
74
|
+
#
|
75
|
+
#############################################################################
|
76
|
+
|
77
|
+
desc "Update version number and gemspec"
|
78
|
+
task :bump do
|
79
|
+
puts "Updated version to #{bump_version}"
|
80
|
+
# Execute does not invoke dependencies.
|
81
|
+
# Manually invoke gemspec then validate.
|
82
|
+
Rake::Task[:gemspec].execute
|
83
|
+
Rake::Task[:validate].execute
|
84
|
+
end
|
85
|
+
|
86
|
+
#############################################################################
|
87
|
+
#
|
88
|
+
# Packaging tasks
|
89
|
+
#
|
90
|
+
#############################################################################
|
91
|
+
|
92
|
+
desc 'Create a release build and push to rubygems'
|
93
|
+
task :release => :build do
|
94
|
+
unless `git branch` =~ /^\* master$/
|
95
|
+
puts "You must be on the master branch to release!"
|
96
|
+
exit!
|
97
|
+
end
|
98
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
99
|
+
sh "git pull --rebase origin master"
|
100
|
+
sh "git tag v#{version}"
|
101
|
+
sh "git push origin master"
|
102
|
+
sh "git push origin v#{version}"
|
103
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
104
|
+
end
|
105
|
+
|
106
|
+
desc 'Publish to rubygems. Same as release'
|
107
|
+
task :publish => :release
|
108
|
+
|
109
|
+
desc 'Build gem'
|
110
|
+
task :build => :gemspec do
|
111
|
+
sh "mkdir -p pkg"
|
112
|
+
sh "gem build #{gemspec_file}"
|
113
|
+
sh "mv #{gem_file} pkg"
|
114
|
+
end
|
115
|
+
|
116
|
+
desc "Build and install"
|
117
|
+
task :install => :build do
|
118
|
+
sh "gem install --local --no-document pkg/#{name}-#{version}.gem"
|
119
|
+
end
|
120
|
+
|
121
|
+
desc 'Update gemspec'
|
122
|
+
task :gemspec => :validate do
|
123
|
+
# read spec file and split out manifest section
|
124
|
+
spec = File.read(gemspec_file)
|
125
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
126
|
+
|
127
|
+
# replace name version and date
|
128
|
+
replace_header(head, :name)
|
129
|
+
replace_header(head, :version)
|
130
|
+
replace_header(head, :date)
|
131
|
+
|
132
|
+
# determine file list from git ls-files
|
133
|
+
files = `git ls-files`.
|
134
|
+
split("\n").
|
135
|
+
sort.
|
136
|
+
reject { |file| file =~ /^\./ }.
|
137
|
+
reject { |file| file =~ /^(rdoc|pkg|test|Home\.md|\.gitattributes)/ }.
|
138
|
+
map { |file| " #{file}" }.
|
139
|
+
join("\n")
|
140
|
+
|
141
|
+
# piece file back together and write
|
142
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
143
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
144
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
145
|
+
puts "Updated #{gemspec_file}"
|
146
|
+
end
|
147
|
+
|
148
|
+
desc 'Validate lib files and version file'
|
149
|
+
task :validate do
|
150
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
151
|
+
unless libfiles.empty?
|
152
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
153
|
+
exit!
|
154
|
+
end
|
155
|
+
unless Dir['VERSION*'].empty?
|
156
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
157
|
+
exit!
|
158
|
+
end
|
159
|
+
end
|
data/bin/cutting_edge
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path('../../lib/cutting_edge/app.rb', __FILE__)
|
4
|
+
require 'yaml'
|
5
|
+
require 'rufus-scheduler'
|
6
|
+
|
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
|
28
|
+
|
29
|
+
options = {
|
30
|
+
:port => 4567,
|
31
|
+
:bind => '0.0.0.0',
|
32
|
+
}
|
33
|
+
|
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
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Need to initialize the log like this once, because otherwise it only becomes available after the Sinatra app has received a request...
|
49
|
+
::SemanticLogger.add_appender(file_name: "#{CuttingEdge::App.environment}.log")
|
50
|
+
|
51
|
+
CuttingEdge::App.set(:repositories, repositories)
|
52
|
+
CuttingEdge::App.set(:store, store)
|
53
|
+
CuttingEdge::App.set(:enable_logging, true)
|
54
|
+
|
55
|
+
puts "Scheduling Jobs..."
|
56
|
+
scheduler = Rufus::Scheduler.new
|
57
|
+
scheduler.every('1h') do
|
58
|
+
worker_fetch_all(repositories.values)
|
59
|
+
end
|
60
|
+
scheduler.every('1h5m') do
|
61
|
+
worker_all_badges(repositories.values)
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "Running Workers a first time..."
|
65
|
+
include CuttingEdgeHelpers
|
66
|
+
worker_fetch_all(repositories.values)
|
67
|
+
|
68
|
+
sleep 5
|
69
|
+
worker_all_badges(repositories.values)
|
70
|
+
|
71
|
+
puts "Starting Sinatra..."
|
72
|
+
CuttingEdge::App.run!(options)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
4
|
+
s.rubygems_version = '1.3.5'
|
5
|
+
s.required_ruby_version = '>= 2.4'
|
6
|
+
|
7
|
+
s.name = 'cutting_edge'
|
8
|
+
s.version = '0.0.1'
|
9
|
+
s.date = '2020-02-19'
|
10
|
+
s.license = 'GPL-3.0-only'
|
11
|
+
|
12
|
+
s.summary = 'Self-hosted dependency monitoring, including shiny badges.'
|
13
|
+
s.description = 'Self-hosted dependency monitoring, including shiny badges.'
|
14
|
+
|
15
|
+
s.authors = ['Dawa Ometto', 'Bart Kamphorst']
|
16
|
+
s.email = 'd.ometto@gmail.com'
|
17
|
+
s.homepage = 'http://github.com/repotag/cutting_edge'
|
18
|
+
|
19
|
+
s.require_paths = %w[lib]
|
20
|
+
|
21
|
+
s.executables = ['cutting_edge']
|
22
|
+
|
23
|
+
s.add_dependency 'gemnasium-parser', '~> 0.1.9'
|
24
|
+
s.add_dependency 'http', '~> 4.3'
|
25
|
+
s.add_dependency 'sucker_punch', '~> 2.1'
|
26
|
+
s.add_dependency 'sinatra', '~> 2.0'
|
27
|
+
s.add_dependency 'moneta', '~> 1.2'
|
28
|
+
s.add_dependency 'victor', '~> 0.2.8'
|
29
|
+
s.add_dependency 'rufus-scheduler', '~> 3.6'
|
30
|
+
s.add_dependency 'sinatra-logger', '~> 0.3'
|
31
|
+
s.add_dependency 'toml-rb', '~> 2.0'
|
32
|
+
# = MANIFEST =
|
33
|
+
s.files = %w[
|
34
|
+
Gemfile
|
35
|
+
LICENSE
|
36
|
+
README.md
|
37
|
+
Rakefile
|
38
|
+
bin/cutting_edge
|
39
|
+
cutting_edge.gemspec
|
40
|
+
lib/cutting_edge.rb
|
41
|
+
lib/cutting_edge/app.rb
|
42
|
+
lib/cutting_edge/badge.rb
|
43
|
+
lib/cutting_edge/langs.rb
|
44
|
+
lib/cutting_edge/langs/python.rb
|
45
|
+
lib/cutting_edge/langs/ruby.rb
|
46
|
+
lib/cutting_edge/langs/rust.rb
|
47
|
+
lib/cutting_edge/repo.rb
|
48
|
+
lib/cutting_edge/versions.rb
|
49
|
+
lib/cutting_edge/workers/badge.rb
|
50
|
+
lib/cutting_edge/workers/dependency.rb
|
51
|
+
lib/cutting_edge/workers/helpers.rb
|
52
|
+
spec/langs/python_spec.rb
|
53
|
+
spec/langs/rust_spec.rb
|
54
|
+
spec/spec_helper.rb
|
55
|
+
]
|
56
|
+
# = MANIFEST =
|
57
|
+
end
|
data/lib/cutting_edge.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sucker_punch'
|
3
|
+
require 'sinatra'
|
4
|
+
require 'sinatra/logger'
|
5
|
+
require 'json'
|
6
|
+
require 'moneta'
|
7
|
+
|
8
|
+
require File.expand_path('../../cutting_edge.rb', __FILE__)
|
9
|
+
require File.expand_path('../repo.rb', __FILE__)
|
10
|
+
require File.expand_path('../workers/dependency.rb', __FILE__)
|
11
|
+
require File.expand_path('../workers/badge.rb', __FILE__)
|
12
|
+
|
13
|
+
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
|
+
|
24
|
+
def worker_fetch_all(repositories)
|
25
|
+
repositories.each do |repo|
|
26
|
+
worker_fetch(repo)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def worker_fetch(repo)
|
31
|
+
DependencyWorker.perform_async(repo.identifier, repo.lang, repo.locations, repo.dependency_types)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
module CuttingEdge
|
37
|
+
LAST_VERSION_TIMEOUT = 5
|
38
|
+
|
39
|
+
class App < Sinatra::Base
|
40
|
+
include CuttingEdgeHelpers
|
41
|
+
|
42
|
+
logger filename: "#{settings.environment}.log", level: :trace
|
43
|
+
|
44
|
+
before do
|
45
|
+
@store = settings.store
|
46
|
+
end
|
47
|
+
|
48
|
+
get %r{/(.+)/(.+)/(.+)/info} do |source, org, name|
|
49
|
+
repo_defined?(source, org, name)
|
50
|
+
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?
|
52
|
+
end
|
53
|
+
|
54
|
+
get %r{/(.+)/(.+)/(.+)/svg} do |source, org, name|
|
55
|
+
repo_defined?(source, org, name)
|
56
|
+
content_type 'image/svg+xml'
|
57
|
+
@store["svg-#{@repo.identifier}"]
|
58
|
+
end
|
59
|
+
|
60
|
+
post %r{/(.+)/(.+)/(.+)/refresh} do |source, org, name|
|
61
|
+
repo_defined?(source, org, name)
|
62
|
+
if @repo.token && params[:token] == @repo.token
|
63
|
+
worker_fetch(@repo)
|
64
|
+
status 200
|
65
|
+
else
|
66
|
+
status 401
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def repo_defined?(source, org, name)
|
73
|
+
halt 404, '404 Not Found' unless @repo = settings.repositories["#{source}/#{org}/#{name}"]
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|