github_bot 0.1.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.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'concerns/response'
5
+
6
+ module GithubBot
7
+ # Public: Base controller for handing rails routes into the GithubBot
8
+ class ApplicationController < ActionController::API
9
+ include Response
10
+ include GithubRequestHelper
11
+
12
+ before_action :valid_request?
13
+ before_action :event_processing
14
+
15
+ # Public: Initial the request for event processing
16
+ def event_processing
17
+ Github::Client.initialize(request)
18
+ end
19
+
20
+ # Public: Returns <true> if the incoming request if properly authorized
21
+ def valid_request?
22
+ signature = github_signature
23
+ my_signature = 'sha1=' + OpenSSL::HMAC.hexdigest(
24
+ OpenSSL::Digest.new('sha1'),
25
+ ENV['GITHUB_WEBHOOK_SECRET'],
26
+ github_payload_raw
27
+ )
28
+
29
+ json_response(json_access_denied, :unauthorized) unless Rack::Utils.secure_compare(my_signature, signature)
30
+ rescue StandardError => e
31
+ msg = "#{self.class}##{__method__} An error occurred while determine if request is valid"
32
+ Rails.logger.error(
33
+ message: msg,
34
+ exception: e
35
+ )
36
+
37
+ json_response(json_access_denied(errors: { message: "#{msg}, exception: #{e.message}" }), :unauthorized)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GithubBot
4
+ # Public: A helper to the controllers for assisting in providing json responses
5
+ module Response
6
+ # Public: Renders a json response
7
+ def json_response(object, status = :ok)
8
+ render json: object, status: status
9
+ end
10
+
11
+ # Public: Returns a json response indicating a 404
12
+ def json_not_found(params)
13
+ {
14
+ errors: {
15
+ message: "Not found with parameter #{params}"
16
+ }
17
+ }
18
+ end
19
+
20
+ # Public: Returns a json response indicating a 403
21
+ def json_access_denied(**args)
22
+ { errors: 'access denied' }.merge(args)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../application_controller'
4
+
5
+ module GithubBot
6
+ module Webhooks
7
+ # Public: The GitHub controller for handling all incoming requests and determining
8
+ # what validator that should be utilized.
9
+ class GithubController < ApplicationController
10
+ # POST /webhooks
11
+ def validate
12
+ return unless pull_request? || check_run?
13
+
14
+ client_api = ::GithubBot::Github::Client.instance
15
+
16
+ client_api.repository_pull_request_bots.each do |bot_class|
17
+ clazz =
18
+ begin
19
+ bot_class.constantize
20
+ rescue StandardError => e
21
+ Rails.logger.error(
22
+ message: "#{self.class}##{__method__} Pull request validator '#{bot_class}' is not defined",
23
+ exception: e
24
+ )
25
+
26
+ nil
27
+ end
28
+
29
+ clazz&.validate
30
+ end
31
+ rescue StandardError => e
32
+ Rails.logger.error message: 'GitHub Bot failure', exception: e
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GithubBot
4
+ # Public: A request helper for understanding the incoming request context
5
+ module GithubRequestHelper
6
+ # Public: Returns the GitHub event type of the incoming request
7
+ def github_event
8
+ request.env['HTTP_X_GITHUB_EVENT']
9
+ end
10
+
11
+ # Public: Returns if the GitHub signature of the incoming request
12
+ def github_signature
13
+ request.env['HTTP_X_HUB_SIGNATURE'] || 'no-signature'
14
+ end
15
+
16
+ # Public: Returns the raw content of the github payload's body content
17
+ def github_payload_raw
18
+ @github_payload_raw ||= request.body.read
19
+ end
20
+
21
+ # Public: Returns the <Json> representation of the github payload
22
+ def github_payload
23
+ return @github_payload if @github_payload
24
+
25
+ begin
26
+ @github_payload = JSON.parse(github_payload_raw).with_indifferent_access
27
+ rescue StandardError => e
28
+ raise StandardError, "Invalid JSON (#{e}): #{@github_payload_raw}"
29
+ end
30
+ end
31
+
32
+ # Public: Returns <true> if the event type is of type 'ping'; otherwise, <false>
33
+ def ping?
34
+ github_event == 'ping'
35
+ end
36
+
37
+ # Public: Return <true> if the event type is of type 'pull_request'; otherwise, <false>
38
+ def pull_request?
39
+ github_event == 'pull_request'
40
+ end
41
+
42
+ # Public: Return <true> if the event type is of type 'pull_request_review'; otherwise, <false>
43
+ def pull_request_review?
44
+ github_event == 'pull_request_review'
45
+ end
46
+
47
+ # Public: Return <true> if the event type is of type 'check_run'; otherwise, <false>
48
+ def check_run?
49
+ github_event == 'check_run'
50
+ end
51
+
52
+ # Public: Return <true> if the action type is of type 'review_requested'; otherwise, <false>
53
+ def review_requested?
54
+ github_event == 'review_requested'
55
+ end
56
+
57
+ # Public: Return <true> if the action type is of type 'review_request_removed'; otherwise, <false>
58
+ def review_request_removed?
59
+ github_event == 'review_request_removed'
60
+ end
61
+
62
+ # Public: Return <true> if the action type is of type 'labeled'; otherwise, <false>
63
+ def labeled?
64
+ github_event == 'labeled'
65
+ end
66
+
67
+ # Public: Return <true> if the action type is of type 'issue_comment'; otherwise, <false>
68
+ def issue_comment?
69
+ github_event == 'issue_comment'
70
+ end
71
+
72
+ # Public: Returns <String> of the comment's body from the github payload
73
+ def comment_body
74
+ github_payload['comment']['body']
75
+ end
76
+
77
+ # Public: Return <true> if the action type is of type 'issue_comment'
78
+ # and the comment message contains "run_validation"; otherwise, <false>
79
+ def issue_comment_recheck?
80
+ github_event == 'issue_comment' && comment_body.include?('run_validation')
81
+ end
82
+
83
+ # Public: Return <true> if issue_comment_recheck? is true
84
+ # and the comment message contains option; otherwise, <false>
85
+ #
86
+ # @param [String] option represents which validation to run
87
+ def recheck_application?(option)
88
+ return false unless issue_comment_recheck?
89
+
90
+ options = recheck_options(comment_body)
91
+ options.include?(option) || options.include?('all')
92
+ end
93
+
94
+ # Public: Return <true> if the action type is of type 'unlabeled'; otherwise, <false>
95
+ def unlabeled?
96
+ github_event == 'unlabeled'
97
+ end
98
+
99
+ # Public: Returns the pull request action type from the payload
100
+ def pull_request_action
101
+ github_payload['action']
102
+ end
103
+
104
+ # Public: Returns the pull request content from the payload
105
+ def pull_request
106
+ github_payload['pull_request']
107
+ end
108
+
109
+ # Public: Returns the repository payload
110
+ def repository
111
+ github_payload['repository']
112
+ end
113
+
114
+ private
115
+
116
+ # Private: Return an array of strings
117
+ #
118
+ # @param str input like "run_validation: a, b, c" with return of ["a","b","c"]
119
+ def recheck_options(str)
120
+ str.gsub(/\s+/, '').partition(':').last.split(',')
121
+ end
122
+ end
123
+ end
data/bin/bundle-audit ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle-audit' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("bundler-audit", "bundle-audit")
data/bin/bundler-audit ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundler-audit' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("bundler-audit", "bundler-audit")
data/bin/rails ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ ENGINE_PATH = File.expand_path('../lib/github_bot/engine', __dir__)
9
+
10
+ # Set up gems listed in the Gemfile.
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
13
+
14
+ require 'rails'
15
+ # Pick the frameworks you want:
16
+ require 'active_model/railtie'
17
+ require 'active_job/railtie'
18
+ require 'active_record/railtie'
19
+ require 'active_storage/engine'
20
+ require 'action_controller/railtie'
21
+ require 'action_mailer/railtie'
22
+ require 'action_view/railtie'
23
+ require 'action_cable/engine'
24
+ require 'sprockets/railtie'
25
+ # require "rails/test_unit/railtie"
26
+ require 'rails/engine/commands'
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
data/bin/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ GithubBot::Engine.routes.draw do
4
+ namespace :webhooks do
5
+ post '/', action: :validate, controller: 'github', defaults: { format: :json }
6
+ end
7
+ end
8
+
9
+ # Adding this to mount the above routes into the application level in order to be able to use
10
+ # the path helpers without having to access them with the main_app.
11
+ Rails.application.routes.draw do
12
+ mount GithubBot::Engine => '/' if GithubBot.draw_routes_in_host_app
13
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'github_bot/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'github_bot'
9
+ spec.version = GithubBot::VERSION
10
+ spec.authors = ['Greg Howdeshell']
11
+ spec.email = ['greg.howdeshell@gmail.com']
12
+
13
+ spec.summary = 'A rubygem designed to assist in the creation of GitHub bot applications.'
14
+ spec.description = <<~DESC
15
+ A rubygem designed to assist in the creation of GitHub bot applications.
16
+ DESC
17
+ spec.homepage = 'https://github.com/cerner/github_bot-ruby'
18
+ spec.license = 'Apache-2.0'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files =
23
+ Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)\//) }
25
+ end
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.required_ruby_version = '>= 2.6.2'
31
+
32
+ spec.add_dependency 'git', '~> 1.0'
33
+ spec.add_dependency 'jwt', '~> 1.0'
34
+ spec.add_dependency 'octokit', '~> 4.18'
35
+ spec.add_dependency 'rails', '>= 5.0.0.1', '< 7.0.0'
36
+ end