# frozen_string_literal: true # Realiza el deploy de un sitio class DeployJob < ApplicationJob class DeployException < StandardError; end class DeployTimedOutException < DeployException; end class DeployAlreadyRunningException < DeployException; end discard_on ActiveRecord::RecordNotFound # Lanzar lo antes posible self.priority = 10 def handle_error(error) case error when DeployAlreadyRunningException then retry_in 1.minute when DeployTimedOutException then expire else super end end # rubocop:disable Metrics/MethodLength def perform(site, notify: true, time: Time.now, output: false) @output = output ActiveRecord::Base.connection_pool.with_connection do @site = Site.find(site) # Si ya hay una tarea corriendo, aplazar esta. Si estuvo # esperando más de 10 minutos, recuperar el estado anterior. # # Como el trabajo actual se aplaza al siguiente, arrastrar la # hora original para poder ir haciendo timeouts. if @site.building? notify = false if 10.minutes.ago >= time raise DeployTimedOutException, "#{@site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original" else raise DeployAlreadyRunningException end end @deployed = {} @site.update status: 'building' @site.deployment_list.each do |d| begin raise DeployException, 'Una dependencia falló' if failed_dependencies? d status = d.deploy(output: @output) seconds = d.build_stats.last.try(:seconds) || 0 size = d.size urls = (d.respond_to?(:urls) ? d.urls : [d.url].compact).map do |url| URI.parse url rescue URI::Error nil end.compact rescue StandardError => e status = false seconds ||= 0 size ||= 0 # XXX: Hace que se vea la tabla urls ||= [nil] notify_exception e, d end @deployed[d.type.underscore.to_sym] = { status: status, seconds: seconds, size: size, urls: urls.map(&:to_s) } end return unless @output puts (Terminal::Table.new do |t| t << (%w[type] + @deployed.values.first.keys) t.add_separator @deployed.each do |type, row| t << ([type.to_s] + row.values) end end) ensure if @site.present? @site.update status: 'waiting' notify_usuaries if notify puts "\a" if @output end end end # rubocop:enable Metrics/MethodLength private # Detecta si un método de publicación tiene dependencias fallidas # # @param :deploy [Deploy] # @return [Boolean] def failed_dependencies?(deploy) failed_dependencies(deploy).present? end # Obtiene las dependencias fallidas de un deploy # # @param :deploy [Deploy] # @return [Array] def failed_dependencies(deploy) deploy.class::DEPENDENCIES & (@deployed.reject do |_, v| v[:status] end.keys) end # @param :exception [StandardError] # @param :deploy [Deploy] def notify_exception(exception, deploy = nil) data = { site: @site.id, deploy: deploy&.type, log: deploy&.build_stats&.last&.log, failed_dependencies: (failed_dependencies(deploy) if deploy) } ExceptionNotifier.notify_exception(exception, data: data) end def notify_usuaries @site.roles.where(rol: 'usuarie', temporal: false).pluck(:usuarie_id).each do |usuarie| DeployMailer.with(usuarie: usuarie, site: @site.id) .deployed(@deployed) .deliver_now end end end