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.
@@ -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