ndr_dev_support 2.1.2 → 3.0.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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +23 -4
  3. data/README.md +24 -31
  4. data/code_safety.yml +99 -19
  5. data/lib/ndr_dev_support/daemon/ci_server.rb +94 -0
  6. data/lib/ndr_dev_support/daemon/stoppable.rb +111 -0
  7. data/lib/ndr_dev_support/integration_testing.rb +25 -3
  8. data/lib/ndr_dev_support/integration_testing/drivers/chrome.rb +9 -0
  9. data/lib/ndr_dev_support/integration_testing/drivers/chrome_headless.rb +17 -0
  10. data/lib/ndr_dev_support/integration_testing/drivers/firefox.rb +9 -0
  11. data/lib/ndr_dev_support/integration_testing/drivers/poltergeist.rb +15 -0
  12. data/lib/ndr_dev_support/integration_testing/drivers/switchable.rb +21 -0
  13. data/lib/ndr_dev_support/integration_testing/dsl.rb +62 -0
  14. data/lib/ndr_dev_support/rake_ci/brakeman_helper.rb +48 -0
  15. data/lib/ndr_dev_support/rake_ci/concerns/commit_metadata_persistable.rb +49 -0
  16. data/lib/ndr_dev_support/rake_ci/simple_cov_helper.rb +26 -0
  17. data/lib/ndr_dev_support/slack_message_publisher.rb +48 -0
  18. data/lib/ndr_dev_support/tasks.rb +12 -0
  19. data/lib/ndr_dev_support/version.rb +1 -1
  20. data/lib/tasks/audit_code.rake +94 -92
  21. data/lib/tasks/ci/brakeman.rake +54 -0
  22. data/lib/tasks/ci/bundle_audit.rake +45 -0
  23. data/lib/tasks/ci/bundle_install.rake +21 -0
  24. data/lib/tasks/ci/housekeep.rake +8 -0
  25. data/lib/tasks/ci/linguist.rake +42 -0
  26. data/lib/tasks/ci/notes.rake +30 -0
  27. data/lib/tasks/ci/prometheus.rake +50 -0
  28. data/lib/tasks/ci/rugged.rake +39 -0
  29. data/lib/tasks/ci/server.rake +12 -0
  30. data/lib/tasks/ci/simplecov.rake +37 -0
  31. data/lib/tasks/ci/slack.rake +30 -0
  32. data/lib/tasks/ci/stats.rake +24 -0
  33. data/ndr_dev_support.gemspec +15 -3
  34. metadata +164 -18
  35. data/lib/ndr_dev_support/integration_testing/capybara.rb +0 -3
  36. data/lib/ndr_dev_support/integration_testing/connection_sharing.rb +0 -47
  37. data/lib/ndr_dev_support/integration_testing/poltergeist.rb +0 -39
  38. data/lib/ndr_dev_support/integration_testing/screenshot.rb +0 -51
@@ -0,0 +1,54 @@
1
+ namespace :ci do
2
+ desc 'Brakeman'
3
+ task brakeman: 'ci:rugged:setup' do
4
+ next unless defined?(Rails)
5
+
6
+ require 'ndr_dev_support/rake_ci/brakeman_helper'
7
+ # Usage: bundle exec rake ci:brakeman
8
+
9
+ @metrics ||= []
10
+ @attachments ||= []
11
+
12
+ brakeman = NdrDevSupport::RakeCI::BrakemanHelper.new
13
+ brakeman.commit = @commit
14
+ brakeman.run
15
+
16
+ Brakeman::Warning::TEXT_CONFIDENCE.each do |confidence, text|
17
+ metric = {
18
+ name: 'brakeman_warnings',
19
+ type: :gauge,
20
+ label_set: { confidence: text },
21
+ value: brakeman.warning_counts_by_confidence[confidence] || 0
22
+ }
23
+ @metrics << metric
24
+ puts metric.inspect
25
+ end
26
+
27
+ unless brakeman.new_fingerprints.empty?
28
+ # new warnings found
29
+ attachment = {
30
+ color: 'danger',
31
+ title: "#{brakeman.new_fingerprints.size} new Brakeman warning(s) :rotating_light:",
32
+ text: '_Brakeman_ warning fingerprint(s):' \
33
+ "```#{brakeman.new_fingerprints.to_a.join("\n")}```",
34
+ footer: 'bundle exec rake ci:brakeman',
35
+ mrkdwn_in: ['text']
36
+ }
37
+ @attachments << attachment
38
+ puts attachment.inspect
39
+ end
40
+
41
+ unless brakeman.old_fingerprints.empty?
42
+ # old warnings missing
43
+ attachment = {
44
+ color: 'good',
45
+ title: "#{brakeman.old_fingerprints.size} Brakeman warning(s) resolved :+1:",
46
+ footer: 'bundle exec rake ci:brakeman'
47
+ }
48
+ @attachments << attachment
49
+ puts attachment.inspect
50
+ end
51
+
52
+ brakeman.save_current_fingerprints
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ namespace :ci do
2
+ # Checks bundle audit and converts advisories into "attachments"
3
+ # Usage: bin/rake ci:bundle_audit
4
+ desc 'Patch-level verification for Bundler'
5
+ task :bundle_audit do
6
+ require 'English'
7
+
8
+ # Update ruby-advisory-db
9
+ `bundle audit update`
10
+ # Check for insecure dependencies
11
+ output = `bundle audit check`
12
+ next if $CHILD_STATUS.exitstatus.zero?
13
+
14
+ output.split("\n\n").each do |advisory|
15
+ lines = advisory.split("\n")
16
+ next if lines.count == 1
17
+
18
+ hash = {}
19
+ lines.each do |line|
20
+ matchdata = line.match(/\A([^:]+):\s(.*)\z/)
21
+ next if matchdata.nil?
22
+
23
+ hash[matchdata[1]] = matchdata[2]
24
+ end
25
+ title = hash.delete('Title')
26
+ url = hash.delete('URL')
27
+ solution = hash.delete('Solution')
28
+ criticality = hash['Criticality']
29
+
30
+ attachment = {
31
+ color: criticality == 'High' ? 'danger' : 'warning',
32
+ fallback: title,
33
+ title: title,
34
+ title_link: url,
35
+ text: solution,
36
+ fields: hash.map { |key, value| { title: key, value: value, short: true } },
37
+ footer: 'bundle exec rake ci:bundle_audit'
38
+ }
39
+
40
+ @attachments ||= []
41
+ @attachments << attachment
42
+ puts attachment.inspect
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ namespace :ci do
2
+ desc 'Install bundled gems'
3
+ task :bundle_install do
4
+ require 'English'
5
+
6
+ # `gem install bundler`
7
+ `bundle install --local`
8
+ next if $CHILD_STATUS.exitstatus.zero?
9
+
10
+ attachment = {
11
+ color: 'danger',
12
+ fallback: 'Failure running bundle install --local',
13
+ text: 'Failure running `bundle install --local`',
14
+ footer: 'bundle exec rake ci:bundle_install'
15
+ }
16
+
17
+ @attachments ||= []
18
+ @attachments << attachment
19
+ puts attachment.inspect
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ namespace :ci do
2
+ desc 'Housekeep cached and temporary files'
3
+ task :housekeep do
4
+ # If running in a Rails project we invoke the standard tmp/ and log/ clearing tasks
5
+ Rake::Task['log:clear'].invoke if Rake::Task.task_defined?('log:clear')
6
+ Rake::Task['tmp:clear'].invoke if Rake::Task.task_defined?('tmp:clear')
7
+ end
8
+ end
@@ -0,0 +1,42 @@
1
+ namespace :ci do
2
+ desc 'linguist'
3
+ task linguist: 'ci:rugged:setup' do
4
+ require 'linguist'
5
+
6
+ def linguist_loc_metrics(language, line_count, percent)
7
+ metrics = [
8
+ {
9
+ name: 'linguist_code_count', type: :gauge,
10
+ label_set: { language: language }, value: line_count
11
+ },
12
+ {
13
+ name: 'linguist_code_percent', type: :gauge,
14
+ label_set: { language: language }, value: percent.round(1)
15
+ }
16
+ ]
17
+ puts metrics.inspect
18
+ metrics
19
+ end
20
+
21
+ @metrics ||= []
22
+
23
+ project = Linguist::Repository.new(@repo, @repo.head.target_id)
24
+ total_line_count = project.languages.values.reduce(:+)
25
+ other_line_count = 0
26
+
27
+ project.languages.each do |language, line_count|
28
+ percent = 100.0 * line_count / total_line_count
29
+
30
+ if percent > 1
31
+ @metrics.concat(linguist_loc_metrics(language, line_count, percent))
32
+ else
33
+ other_line_count += line_count
34
+ end
35
+ end
36
+
37
+ if other_line_count > 0
38
+ other_percent = 100.0 * other_line_count / total_line_count
39
+ @metrics.concat(linguist_loc_metrics('Other', other_line_count, other_percent))
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ namespace :ci do
2
+ # Runs the Rails rake notes task (if using Rails) and converts annotation counts into "metrics"
3
+ # Usage: bundle exec rake ci:notes
4
+ desc 'Count notes and other annotations'
5
+ task :notes do
6
+ next unless Rake::Task.task_defined?('notes')
7
+
8
+ hash = {}
9
+ `bundle exec rake notes | grep "\\["`.split(/\n/).map do |line|
10
+ matchdata = line.match(/\[\s*\d+\] \[([^\]]+)\]/)
11
+ annotation = matchdata[1]
12
+ hash[annotation] ||= 0
13
+ hash[annotation] += 1
14
+ end
15
+
16
+ hash.each do |annotation, count|
17
+ metric = {
18
+ name: 'annotation_count',
19
+ type: :gauge,
20
+ label_set: {
21
+ annotation: annotation
22
+ },
23
+ value: count
24
+ }
25
+ @metrics ||= []
26
+ @metrics << metric
27
+ puts metric.inspect
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ namespace :ci do
2
+ namespace :prometheus do
3
+ desc 'Set up Prometheus'
4
+ task :setup do
5
+ @metrics = []
6
+
7
+ ENV['PROMETHEUS_PUSHGATEWAY'] ||= ask('Prometheus pushgateway (host:port): ')
8
+ ENV['PROMETHEUS_PUSHGATEWAY'] = nil if ENV['PROMETHEUS_PUSHGATEWAY'] == ''
9
+
10
+ ENV['PROMETHEUS_PROJECTNAME'] ||= ask('Prometheus project name: ')
11
+ ENV['PROMETHEUS_PROJECTNAME'] = nil if ENV['PROMETHEUS_PROJECTNAME'] == ''
12
+ end
13
+
14
+ desc 'Push Prometheus stats'
15
+ task publish: :setup do
16
+ next if @metrics.empty? || ENV['PROMETHEUS_PUSHGATEWAY'].nil?
17
+
18
+ require 'prometheus/client'
19
+ require 'prometheus/client/push'
20
+
21
+ # returns a default registry
22
+ prometheus = Prometheus::Client.registry
23
+
24
+ @metrics.each do |metric|
25
+ name = "ci_#{metric[:name]}".to_sym
26
+ # TODO: Add :docstring where required
27
+ docstring = metric[:docstring] || 'TODO'
28
+ label_set = metric[:label_set] || {}
29
+ value = metric[:value]
30
+
31
+ case metric[:type]
32
+ when :gauge
33
+ gauge =
34
+ if prometheus.exist?(name)
35
+ prometheus.get(name)
36
+ else
37
+ prometheus.gauge(name, docstring, project: ENV['PROMETHEUS_PROJECTNAME'])
38
+ end
39
+ gauge.set(label_set, value)
40
+ else
41
+ raise "Unknown metric type (#{metric.inspect})"
42
+ end
43
+ end
44
+
45
+ Prometheus::Client::Push.new(
46
+ 'rake-ci-job', nil, ENV['PROMETHEUS_PUSHGATEWAY']
47
+ ).add(prometheus)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,39 @@
1
+ namespace :ci do
2
+ namespace :rugged do
3
+ desc 'Setup Rugged, get the current commit and friendly version of the revision name'
4
+ task :setup do
5
+ require 'ndr_dev_support/daemon/ci_server'
6
+
7
+ @repo = Rugged::Repository.new('.')
8
+ @commit = @repo.lookup(@repo.head.target_id)
9
+ @friendly_revision_name = NdrDevSupport::Daemon::CIServer.friendly_revision_name(@commit)
10
+ end
11
+
12
+ desc 'Show details of the commit and make it the first attachment'
13
+ task commit_details: :setup do
14
+ fields = [
15
+ { title: 'Author', value: @commit.author[:name], short: true },
16
+ { title: 'Revision', value: @friendly_revision_name, short: true }
17
+ ]
18
+
19
+ if File.exist?('.ruby-version')
20
+ fields << {
21
+ title: 'Target Ruby Version',
22
+ value: File.read('.ruby-version').chomp,
23
+ short: true
24
+ }
25
+ end
26
+
27
+ attachment = {
28
+ fallback: @commit.message,
29
+ text: @commit.message,
30
+ fields: fields,
31
+ ts: @commit.author[:time].to_i
32
+ }
33
+
34
+ @attachments ||= []
35
+ @attachments.unshift(attachment)
36
+ puts attachment.inspect
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,12 @@
1
+ namespace :ci do
2
+ # Stub ci:all rake task
3
+ task :all
4
+
5
+ desc 'Runs the Rake CI server'
6
+ task :server do
7
+ require 'ndr_dev_support/daemon/ci_server'
8
+
9
+ worker = NdrDevSupport::Daemon::CIServer.from_args(ENV)
10
+ worker.run
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ namespace :ci do
2
+ namespace :simplecov do
3
+ desc 'setup'
4
+ task :setup do
5
+ # Usage: bundle exec rake ci:simplecov:setup test
6
+ require 'simplecov'
7
+ require 'ndr_dev_support/rake_ci/simple_cov_helper'
8
+
9
+ SimpleCov.at_exit do
10
+ result = SimpleCov.result
11
+ result.format! if ENV['RAKECI_HEADLESS'].nil?
12
+ NdrDevSupport::RakeCI::SimpleCovHelper.new.save_current_result(result)
13
+ end
14
+
15
+ SimpleCov.start
16
+ end
17
+
18
+ desc 'process'
19
+ task :process do
20
+ require 'simplecov'
21
+ require 'ndr_dev_support/rake_ci/simple_cov_helper'
22
+
23
+ helper = NdrDevSupport::RakeCI::SimpleCovHelper.new
24
+ result = helper.load_current_result
25
+ next if result.nil?
26
+
27
+ metrics = [
28
+ { name: 'simplecov_covered_percent', type: :gauge, value: result.covered_percent },
29
+ { name: 'simplecov_covered_lines', type: :gauge, value: result.covered_lines },
30
+ { name: 'simplecov_total_lines', type: :gauge, value: result.total_lines }
31
+ ]
32
+ @metrics ||= []
33
+ @metrics.concat(metrics)
34
+ puts metrics.inspect
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ namespace :ci do
2
+ namespace :slack do
3
+ desc 'Set up Slack'
4
+ task :setup do
5
+ @attachments = []
6
+
7
+ ENV['SLACK_WEBHOOK_URL'] ||= ask('Slack Webhook URL: ')
8
+ ENV['SLACK_WEBHOOK_URL'] = nil if ENV['SLACK_WEBHOOK_URL'] == ''
9
+
10
+ ENV['SLACK_CHANNEL'] ||= ask('Slack Channel: ')
11
+ ENV['SLACK_CHANNEL'] = nil if ENV['SLACK_CHANNEL'] == ''
12
+ end
13
+
14
+ desc 'publish'
15
+ task publish: :setup do
16
+ next if @attachments.empty? || ENV['SLACK_WEBHOOK_URL'].nil?
17
+
18
+ require 'ndr_dev_support/slack_message_publisher'
19
+
20
+ # We have attachments so prepend them with basic commit details
21
+ Rake::Task['ci:rugged:commit_details'].invoke
22
+
23
+ slack_publisher = NdrDevSupport::SlackMessagePublisher.new(ENV['SLACK_WEBHOOK_URL'],
24
+ username: 'Rake CI',
25
+ icon_emoji: ':robot_face:',
26
+ channel: ENV['SLACK_CHANNEL'])
27
+ slack_publisher.post(attachments: @attachments)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ namespace :ci do
2
+ # Runs the Rails rake stats task (if using Rails) and converts counts into "metrics"
3
+ # Usage: bundle exec rake ci:stats
4
+ desc 'stats'
5
+ task :stats do
6
+ next unless Rake::Task.task_defined?('stats')
7
+
8
+ @metrics ||= []
9
+
10
+ summary_line = `bundle exec rake stats 2>/dev/null | tail -n 2 | head -n 1`
11
+ matchdata = summary_line.match(/Code LOC: (\d+)\b\s+Test LOC: (\d+)\b/)
12
+ code_loc = matchdata[1].to_i
13
+ test_loc = matchdata[2].to_i
14
+ test_ratio = 100.0 * test_loc / code_loc
15
+
16
+ metrics = [
17
+ { name: 'stats_code_loc', type: :gauge, value: code_loc },
18
+ { name: 'stats_test_loc', type: :gauge, value: test_loc },
19
+ { name: 'stats_test_ratio', type: :gauge, value: test_ratio }
20
+ ]
21
+ @metrics.concat(metrics)
22
+ puts metrics.inspect
23
+ end
24
+ end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'ndr_dev_support/version'
@@ -22,17 +21,30 @@ Gem::Specification.new do |spec|
22
21
 
23
22
  spec.add_dependency 'pry'
24
23
 
24
+ # Audit dependencies:
25
+ spec.add_dependency 'highline', '>= 1.6.0'
26
+
25
27
  # Rubocop dependencies:
26
- spec.add_dependency 'rubocop', '0.52.1'
27
28
  spec.add_dependency 'parser'
28
29
  spec.add_dependency 'rainbow'
30
+ spec.add_dependency 'rubocop', '0.52.1'
29
31
 
30
32
  # Integration test dependencies:
31
33
  spec.add_dependency 'capybara'
32
34
  spec.add_dependency 'capybara-screenshot'
35
+ spec.add_dependency 'chromedriver-helper'
33
36
  spec.add_dependency 'poltergeist', '>= 1.8.0'
37
+ spec.add_dependency 'selenium-webdriver'
38
+
39
+ # CI server dependencies:
40
+ spec.add_dependency 'activesupport', '< 6.0.0'
41
+ spec.add_dependency 'brakeman', '>= 4.2.0'
42
+ spec.add_dependency 'bundler-audit'
43
+ spec.add_dependency 'github-linguist'
44
+ spec.add_dependency 'prometheus-client'
45
+ spec.add_dependency 'rugged'
34
46
 
35
47
  spec.add_development_dependency 'bundler', '~> 1.7'
48
+ spec.add_development_dependency 'minitest', '~> 5.0'
36
49
  spec.add_development_dependency 'rake', '~> 10.0'
37
- spec.add_development_dependency "minitest", "~> 5.0"
38
50
  end