mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-16 16:41:42 +00:00
enviar reportes a gitlab usando la api en lugar del correo
This commit is contained in:
parent
691f064a24
commit
fc7c2f31dd
9 changed files with 226 additions and 11 deletions
1
Gemfile
1
Gemfile
|
@ -41,6 +41,7 @@ gem 'hiredis'
|
||||||
gem 'image_processing'
|
gem 'image_processing'
|
||||||
gem 'icalendar'
|
gem 'icalendar'
|
||||||
gem 'inline_svg'
|
gem 'inline_svg'
|
||||||
|
gem 'httparty'
|
||||||
gem 'safe_yaml', source: 'https://gems.sutty.nl'
|
gem 'safe_yaml', source: 'https://gems.sutty.nl'
|
||||||
gem 'jekyll', '~> 4.2'
|
gem 'jekyll', '~> 4.2'
|
||||||
gem 'jekyll-data', source: 'https://gems.sutty.nl'
|
gem 'jekyll-data', source: 'https://gems.sutty.nl'
|
||||||
|
|
|
@ -646,6 +646,7 @@ DEPENDENCIES
|
||||||
haml-lint
|
haml-lint
|
||||||
hamlit-rails
|
hamlit-rails
|
||||||
hiredis
|
hiredis
|
||||||
|
httparty
|
||||||
icalendar
|
icalendar
|
||||||
image_processing
|
image_processing
|
||||||
inline_svg
|
inline_svg
|
||||||
|
|
|
@ -40,7 +40,7 @@ class BacktraceJob < ApplicationJob
|
||||||
begin
|
begin
|
||||||
raise BacktraceException, "#{origin}: #{message}"
|
raise BacktraceException, "#{origin}: #{message}"
|
||||||
rescue BacktraceException => e
|
rescue BacktraceException => e
|
||||||
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, _backtrace: true })
|
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, javascript_backtrace: true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
142
app/jobs/gitlab_notifier_job.rb
Normal file
142
app/jobs/gitlab_notifier_job.rb
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class GitlabNotifierJob < ApplicationJob
|
||||||
|
include ExceptionNotifier::BacktraceCleaner
|
||||||
|
|
||||||
|
attr_reader :exception, :options
|
||||||
|
|
||||||
|
queue_as :low_priority
|
||||||
|
|
||||||
|
def perform(exception, **options)
|
||||||
|
@exception = exception
|
||||||
|
@options = options
|
||||||
|
|
||||||
|
Rails.logger.info 'Enviando reporte a Gitlab'
|
||||||
|
|
||||||
|
i = client.new_issue confidential: true, title: title, description: description
|
||||||
|
|
||||||
|
Rails.logger.info "Enviado reporte a Gitlab: #{i['iid']}"
|
||||||
|
rescue Exception => e
|
||||||
|
Rails.logger.info 'No entrar en loop'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Define si es una excepción de javascript o local
|
||||||
|
#
|
||||||
|
# @see BacktraceJob
|
||||||
|
def javascript?
|
||||||
|
@javascript ||= options.dig(:data, :javascript_backtrace).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [String]
|
||||||
|
def title
|
||||||
|
@title ||= ''.dup.tap do |t|
|
||||||
|
t << "[#{exception.class}] " unless javascript?
|
||||||
|
t << exception.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [String]
|
||||||
|
def description
|
||||||
|
@description ||= ''.dup.tap do |d|
|
||||||
|
d << request_section
|
||||||
|
d << javascript_section
|
||||||
|
d << javascript_footer
|
||||||
|
d << backtrace_section
|
||||||
|
d << data_section
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [String,Nil]
|
||||||
|
def backtrace
|
||||||
|
@backtrace ||= exception.backtrace ? clean_backtrace(exception) : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def env
|
||||||
|
options[:env]
|
||||||
|
end
|
||||||
|
|
||||||
|
def request
|
||||||
|
@request ||= ActionDispatch::Request.new(env) if env.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [GitlabApiClient]
|
||||||
|
def client
|
||||||
|
@client ||= GitlabApiClient.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_section
|
||||||
|
return '' unless request
|
||||||
|
|
||||||
|
<<~REQUEST
|
||||||
|
|
||||||
|
# Request
|
||||||
|
|
||||||
|
```
|
||||||
|
#{request.request_method} #{request.url}#{' '}
|
||||||
|
|
||||||
|
#{pp request.filtered_parameters}
|
||||||
|
```
|
||||||
|
|
||||||
|
REQUEST
|
||||||
|
end
|
||||||
|
|
||||||
|
def javascript_section
|
||||||
|
return '' unless javascript?
|
||||||
|
|
||||||
|
options.dig(:data, :params, 'errors')&.map do |error|
|
||||||
|
<<~JAVASCRIPT
|
||||||
|
|
||||||
|
## #{error['type'] || 'NoError'}: #{error['message']}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
#{Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values)}
|
||||||
|
```
|
||||||
|
|
||||||
|
JAVASCRIPT
|
||||||
|
end&.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def javascript_footer
|
||||||
|
return '' unless javascript?
|
||||||
|
|
||||||
|
<<~JAVASCRIPT
|
||||||
|
|
||||||
|
#{options.dig(:data, :params, 'context', 'userAgent')}
|
||||||
|
|
||||||
|
<#{options.dig(:data, :params, 'context', 'url')}>
|
||||||
|
|
||||||
|
JAVASCRIPT
|
||||||
|
end
|
||||||
|
|
||||||
|
def backtrace_section
|
||||||
|
return '' if javascript?
|
||||||
|
return '' unless backtrace
|
||||||
|
|
||||||
|
<<~BACKTRACE
|
||||||
|
|
||||||
|
## Backtrace
|
||||||
|
|
||||||
|
```
|
||||||
|
#{backtrace.join("\n")}
|
||||||
|
```
|
||||||
|
|
||||||
|
BACKTRACE
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_section
|
||||||
|
return '' unless options[:data]
|
||||||
|
|
||||||
|
<<~DATA
|
||||||
|
|
||||||
|
## Data
|
||||||
|
|
||||||
|
```
|
||||||
|
#{pp options[:data]}
|
||||||
|
```
|
||||||
|
|
||||||
|
DATA
|
||||||
|
end
|
||||||
|
end
|
17
app/lib/exception_notifier/gitlab_notifier.rb
Normal file
17
app/lib/exception_notifier/gitlab_notifier.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ExceptionNotifier
|
||||||
|
# Notifica las excepciones como incidencias en Gitlab
|
||||||
|
class GitlabNotifier
|
||||||
|
def initialize(_); end
|
||||||
|
|
||||||
|
# Recibe la excepción y empieza la tarea de notificación en segundo
|
||||||
|
# plano.
|
||||||
|
#
|
||||||
|
# @param [Exception]
|
||||||
|
# @param [Hash]
|
||||||
|
def call(exception, **options)
|
||||||
|
GitlabNotifierJob.perform_async(exception, **options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
61
app/lib/gitlab_api_client.rb
Normal file
61
app/lib/gitlab_api_client.rb
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'httparty'
|
||||||
|
|
||||||
|
class GitlabApiClient
|
||||||
|
include HTTParty
|
||||||
|
|
||||||
|
# TODO: Hacer configurable por sitio
|
||||||
|
base_uri ENV.fetch('GITLAB_URI', 'https://0xacab.org')
|
||||||
|
no_follow true
|
||||||
|
|
||||||
|
# Trae todos los proyectos. Como estamos usando un Project Token,
|
||||||
|
# siempre va a traer uno solo.
|
||||||
|
#
|
||||||
|
# @return [HTTParty::Response]
|
||||||
|
def projects
|
||||||
|
self.class.get('/api/v4/projects', { query: params(membership: true) })
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el identificador del proyecto
|
||||||
|
#
|
||||||
|
# @return [Integer]
|
||||||
|
def project_id
|
||||||
|
@project_id ||= ENV['GITLAB_PROJECT'] || projects&.first&.dig('id')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Crea un issue
|
||||||
|
#
|
||||||
|
# @see https://docs.gitlab.com/ee/api/issues.html#new-issue
|
||||||
|
# @return [HTTParty::Response]
|
||||||
|
def new_issue(**args)
|
||||||
|
self.class.post("/api/v4/projects/#{project_id}/issues", { query: params(**args) })
|
||||||
|
end
|
||||||
|
|
||||||
|
# Modifica un issue
|
||||||
|
#
|
||||||
|
# @see https://docs.gitlab.com/ee/api/issues.html#edit-issue
|
||||||
|
# @return [HTTParty::Response]
|
||||||
|
def edit_issue(**args)
|
||||||
|
self.class.put("/api/v4/projects/#{project_id}/issues", { query: params(**args) })
|
||||||
|
end
|
||||||
|
|
||||||
|
# Crea un comentario
|
||||||
|
#
|
||||||
|
# @see https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note
|
||||||
|
# @return [HTTParty::Response]
|
||||||
|
def new_note(iid:, **args)
|
||||||
|
self.class.post("/api/v4/projects/#{project_id}/issues/#{iid}/notes", { query: params(**args) })
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def params(**args)
|
||||||
|
default_params.merge(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Que cada sitio tenga su propio token y uri
|
||||||
|
def default_params
|
||||||
|
{ private_token: ENV['GITLAB_TOKEN'] }
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
<% unless @data[:_backtrace] %>
|
<% unless @data[:javascript_backtrace] %>
|
||||||
```
|
```
|
||||||
<%= raw @backtrace.join("\n") %>
|
<%= raw @backtrace.join("\n") %>
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<% if @data[:_backtrace] %>
|
<% if @data[:javascript_backtrace] %>
|
||||||
<% @data.dig(:params, 'errors')&.each do |error| %>
|
<% @data.dig(:params, 'errors')&.each do |error| %>
|
||||||
# <%= error['type'] %>: <%= error['message'] %>
|
# <%= error['type'] %>: <%= error['message'] %>
|
||||||
|
|
||||||
|
|
|
@ -147,14 +147,7 @@ Rails.application.configure do
|
||||||
}
|
}
|
||||||
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
||||||
|
|
||||||
config.middleware.use ExceptionNotification::Rack,
|
config.middleware.use ExceptionNotification::Rack, gitlab: {}
|
||||||
error_grouping: true,
|
|
||||||
email: {
|
|
||||||
email_prefix: '',
|
|
||||||
sender_address: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl"),
|
|
||||||
exception_recipients: ENV.fetch('EXCEPTION_TO', "errors@sutty.nl"),
|
|
||||||
normalize_subject: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
||||||
Rails.application.routes.default_url_options[:protocol] = 'https'
|
Rails.application.routes.default_url_options[:protocol] = 'https'
|
||||||
|
|
Loading…
Reference in a new issue