cutting_edge 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ [![Build Status](https://travis-ci.org/repotag/cutting_edge.svg?branch=master)](https://travis-ci.org/repotag/cutting_edge)
2
+ [![Coverage Status](https://coveralls.io/repos/github/repotag/cutting_edge/badge.svg?branch=master)](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).
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module CuttingEdge
2
+ VERSION = '0.0.1'
3
+ end
@@ -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