# frozen_string_literal: true require 'distributed_press/v1/client/site' require 'njalla/v1' # Soportar Distributed Press APIv1 # # Usa tokens de publicación efímeros para todas las acciones. # # Al ser creado, genera el sitio en la instancia de Distributed Press # configurada y almacena el ID. # # Al ser publicado, envía los archivos en un tarball y actualiza la # información. class DeployDistributedPress < Deploy store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON before_create :create_remote_site!, :create_njalla_records! before_destroy :delete_remote_site!, :delete_njalla_records! DEPENDENCIES = %i[deploy_local] # Actualiza la información y luego envía los cambios # # @param :output [Bool] # @return [Bool] def deploy status = false log = [] time_start create_remote_site! if remote_site_id.blank? create_njalla_records! save if remote_site_id.blank? raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press' end if create_njalla_records? && remote_info[:njalla].blank? raise DeployJob::DeployException, 'No se pudieron crear los registros necesarios en Njalla' end site_client.tap do |c| stdout = Thread.new(publisher.logger_out) do |io| until io.eof? line = io.gets puts line if output log << line end end status = c.publish(publishing_site, deploy_local.destination) if status self.remote_info[:distributed_press] = c.show(publishing_site).to_h save end publisher.logger.close stdout.join end time_stop create_stat! status, log.join status end def limit; end def size deploy_local.size end def destination; end # Devuelve las URLs de todos los protocolos def urls protocol_urls + gateway_urls end private def gateway_urls remote_info.dig(:distributed_press, :links).values.map do |protocol| [ protocol[:link], protocol[:gateway] ] end.flatten.compact.select do |link| link.include? '://' end end def protocol_urls remote_info.dig(:distributed_press, :protocols).select do |_, enabled| enabled end.map do |protocol, _| "#{protocol}://#{site.hostname}" end end # El cliente de la API # # TODO: cuando soportemos más, tiene que haber una relación entre # DeployDistributedPress y DistributedPressPublisher. # # @return [DistributedPressPublisher] def publisher @publisher ||= DistributedPressPublisher.last end # El cliente para actualizar el sitio # # @return [DistributedPress::V1::Client::Site] def site_client DistributedPress::V1::Client::Site.new(publisher.client) end # Genera el esquema de datos para poder publicar el sitio # # @return [DistributedPress::V1::Schemas::PublishingSite] def publishing_site DistributedPress::V1::Schemas::PublishingSite.new.call(id: remote_site_id) end # Genera el esquema de datos para crear el sitio # # @return [DistributedPressPublisher::V1::Schemas::NewSite] def create_site DistributedPress::V1::Schemas::NewSite.new.call(domain: hostname, protocols: { http: true, ipfs: true, hyper: true }) end # Crea el sitio en la instancia con el hostname especificado # # @return [nil] def create_remote_site! created_site = site_client.create(create_site) self.remote_site_id = created_site[:id] self.remote_info ||= {} self.remote_info[:distributed_press] = created_site.to_h nil rescue DistributedPress::V1::Error => e ExceptionNotifier.notify_exception(e, data: { site: site.name }) nil end # Crea los registros en Njalla # # XXX: Esto depende de nuestro DNS actual, cuando lo migremos hay # que eliminarlo. # # @return [nil] def create_njalla_records! return unless create_njalla_records? self.remote_info ||= {} self.remote_info[:njalla] ||= {} self.remote_info[:njalla][:a] ||= njalla.add_record(name: site.name, type: 'CNAME', content: "#{Site.domain}.").to_h self.remote_info[:njalla][:cname] ||= njalla.add_record(name: "www.#{site.name}", type: 'CNAME', content: "#{Site.domain}.").to_h self.remote_info[:njalla][:ns] ||= njalla.add_record(name: "_dnslink.#{site.name}", type: 'NS', content: "#{publisher.hostname}.").to_h nil rescue HTTParty::Error => e ExceptionNotifier.notify_exception(e, data: { site: site.name }) self.remote_info.delete :njalla ensure nil end # Registra lo que sucedió # # @param status [Bool] # @param log [String] # @return [nil] def create_stat!(status, log) build_stats.create action: publisher.to_s,log: log, seconds: time_spent_in_seconds, bytes: size, status: status nil end def delete_remote_site! site_client.delete(publishing_site) nil rescue DistributedPress::V1::Error => e ExceptionNotifier.notify_exception(e, data: { site: site.name }) nil end def delete_njalla_records! return unless create_njalla_records? %w[a ns cname].each do |type| next if (id = remote_info.dig('njalla', type, 'id')).blank? njalla.remove_record(id: id.to_i) end end # Actualizar registros en Njalla # # @return [Njalla::V1::Domain] def njalla @njalla ||= begin client = Njalla::V1::Client.new(token: Rails.application.credentials.njalla) Njalla::V1::Domain.new(domain: Site.domain, client: client) end end # Detecta si tenemos que crear registros en Njalla def create_njalla_records? !site.name.end_with?('.') end end