lib/autoload/kuroko2/workflow/notifier/webhook.rb



module Kuroko2
  module Workflow
    module Notifier
      class Webhook
        attr_reader :message_builder

        HASH_ALGORITHM = 'sha256'
        HMAC_DIGEST    = OpenSSL::Digest.new(HASH_ALGORITHM)

        def initialize(instance)
          @instance   = instance
          @definition = instance.job_definition
          @message_builder = Workflow::Notifier::Concerns::ChatMessageBuilder.new(instance)
          @secret_token = Kuroko2.config.notifiers.webhook.try!(:secret_token)
        end

        def notify_launch
          if @definition.hipchat_notify_finished?
            request(
              build_payload(
                action: 'notify_launch',
                level: 'INFO',
                subject: message_builder.launched_text,
                message: @instance.logs.reverse.detect{ |log| log.level == 'INFO' }.try!(:message),
              )
            )
          end
        end

        def notify_retrying
          if @definition.hipchat_notify_finished?
            request(
              build_payload(
                action: 'notify_retrying',
                level: 'INFO',
                subject: message_builder.retrying_text,
                message: @instance.logs.last(2).first.message,
              )
            )
          end
        end

        def notify_skipping
          if @definition.hipchat_notify_finished?
            request(
              build_payload(
                action: 'notify_skipping',
                level: 'INFO',
                subject: message_builder.skipping_text,
                message: @instance.logs.last(2).first.message,
              )
            )
          end
        end

        def notify_cancellation
          if @definition.notify_cancellation || @definition.hipchat_notify_finished?
            request(
              build_payload(
                action: 'notify_cancellation',
                level: 'WARNING',
                subject: message_builder.failure_text,
                message: @instance.logs.reverse.detect{ |log| log.level == 'WARN' }.try!(:message),
              )
            )
          end
        end

        def notify_failure
          request(
            build_payload(
              action: 'notify_failure',
              level: 'FAILURE',
              subject: message_builder.failure_text,
              message: @instance.logs.last(2).first.message,
            )
          )
        end

        def notify_critical
          request(
            build_payload(
              action: 'notify_critical',
              level: 'CRITICAL',
              subject: message_builder.failure_text,
              message: @instance.logs.last(2).first.message,
            )
          )
        end

        def notify_finished
          if @definition.hipchat_notify_finished?
            request(
              build_payload(
                action: 'notify_finished',
                level: 'SUCCESS',
                subject: message_builder.finished_text,
              )
            )
          end
        end

        def notify_back_to_normal
          request(
            build_payload(
              action: 'notify_back_to_normal',
              level: 'SUCCESS',
              subject: message_builder.back_to_normal_text,
            )
          )
        end

        def notify_long_elapsed_time
          request(
            build_payload(
              action: 'notify_long_elapsed_time',
              level: 'WARNING',
              subject: message_builder.long_elapsed_time_text,
            )
          )
        end

        private

        def request(body)
          return unless @definition.webhook_url.present?

          url = URI.parse(@definition.webhook_url)
          conn = Faraday.new(url: "#{url.scheme}://#{url.host}") do |faraday|
            faraday.port = url.port
            faraday.adapter Faraday.default_adapter
          end

          json = body.to_json
          response = conn.post do |req|
            req.url(url.path)

            req.headers['X-Kuroko2-Id'] = SecureRandom.uuid
            if @secret_token.present?
              req.headers['X-Kuroko2-Signature'] = "#{HASH_ALGORITHM}=#{OpenSSL::HMAC.hexdigest(HMAC_DIGEST, @secret_token, json)}"
            end

            req.headers['User-Agent']   = 'Kuroko2-Webhook'
            req.headers['Content-Type'] = 'application/json'
            req.body = json
          end

          unless response.success?
            Kuroko2.logger.fatal("Failure sending webhook: status=#{response.status} body=#{response.body}")
          end
        end

        def build_payload(action:, level:, subject:, message: nil)
          {
            action: action,
            level: level,
            subject: subject,
            message: message,
            job_instance: {
              url: message_builder.job_instance_path,
              id: @instance.id,
              script: @instance.script,
              finished_at: @instance.finished_at.try!(:iso8601),
              canceled_at: @instance.canceled_at.try!(:iso8601),
              error_at: @instance.error_at.try!(:iso8601),
              created_at: @instance.created_at.try!(:iso8601),
            },
            job_definition: {
              url: Kuroko2::Engine.routes.url_helpers.job_definition_url(
                @definition,
                host: Kuroko2.config.url_host,
                protocol: Kuroko2.config.url_scheme,
              ),
              id: @definition.id,
              name: @definition.name,
              description: @definition.description,
            }
          }
        end
      end
    end
  end
end