# 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 retry_on DeployAlreadyRunningException, wait: 1.minute discard_on DeployTimedOutException # rubocop:disable Metrics/MethodLength def perform(site, notify: true, time: Time.now, output: false) @site = site ActiveRecord::Base.connection_pool.with_connection do # 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 raise DeployAlreadyRunningException unless 10.minutes.ago >= time raise DeployTimedOutException, "#{site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original" 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.urls.map do |url| URI.parse url rescue URI::Error nil end.compact raise DeployException, 'Falló la compilación' if d == site.deployment_list.last && !status 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 } 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.name, 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 usuarie_ids = site.roles.where(rol: 'usuarie', temporal: false).pluck(:usuarie_id) Usuarie.where(id: usuarie_ids).find_each do |usuarie| DeployMailer.with(usuarie: usuarie, site: site) .deployed(@deployed) .deliver_now end end end