summaryrefslogtreecommitdiff
path: root/lib/action/custom.rb
blob: a2f3d59c82f1815b575a3655d74cc76ee54cb412 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'base64'

require_relative '../http_helper'

module Action
  class Custom
    include HTTPHelper

    class BaseError < StandardError; end
    class MissingPayloadError < BaseError; end
    class MissingAPIEndpointsError < BaseError; end
    class MissingDataError < BaseError; end
    class UnsuccessfulError < BaseError; end

    NO_MESSAGE_TEXT = 'No message'.freeze
    DEFAULT_HEADERS = { 'Content-Type' => CONTENT_TYPE_JSON }.freeze

    def initialize(gl_id, payload)
      @gl_id = gl_id
      @payload = payload
    end

    def execute
      validate!
      inform_client(info_message) if info_message
      process_api_endpoints!
    end

    private

    attr_reader :gl_id, :payload

    def process_api_endpoints!
      output = ''
      resp = nil

      data_with_gl_id = data.merge('gl_id' => gl_id)

      api_endpoints.each do |endpoint|
        url = "#{base_url}#{endpoint}"
        json = { 'data' => data_with_gl_id, 'output' => output }

        resp = post(url, {}, headers: DEFAULT_HEADERS, options: { json: json })

        # Net::HTTPSuccess is the parent of Net::HTTPOK, Net::HTTPCreated etc.
        case resp
        when Net::HTTPSuccess, Net::HTTPMultipleChoices
          true
        else
          raise_unsuccessful!(resp)
        end

        begin
          body = JSON.parse(resp.body)
        rescue JSON::ParserError
          raise UnsuccessfulError, 'Response was not valid JSON'
        end

        print_flush(body['result'])

        # In the context of the git push sequence of events, it's necessary to read
        # stdin in order to capture output to pass onto subsequent commands
        output = read_stdin
      end

      resp
    end

    def base_url
      config.gitlab_url
    end

    def data
      @data ||= payload['data']
    end

    def api_endpoints
      data['api_endpoints']
    end

    def info_message
      data['info_message']
    end

    def config
      @config ||= GitlabConfig.new
    end

    def api
      @api ||= GitlabNet.new
    end

    def read_stdin
      Base64.encode64($stdin.read)
    end

    def print_flush(str)
      return false unless str
      $stdout.print(Base64.decode64(str))
      $stdout.flush
    end

    def inform_client(str)
      $stderr.puts(format_gitlab_output(str))
    end

    def format_gitlab_output(str)
      str.split("\n").map { |line| "> GitLab: #{line}" }.join("\n")
    end

    def validate!
      validate_payload!
      validate_data!
      validate_api_endpoints!
    end

    def validate_payload!
      raise MissingPayloadError if !payload.is_a?(Hash) || payload.empty?
    end

    def validate_data!
      raise MissingDataError unless data.is_a?(Hash)
    end

    def validate_api_endpoints!
      raise MissingAPIEndpointsError if !api_endpoints.is_a?(Array) ||
                                        api_endpoints.empty?
    end

    def raise_unsuccessful!(result)
      message = "#{exception_message_for(result.body)} (#{result.code})"
      raise UnsuccessfulError, format_gitlab_output(message)
    end

    def exception_message_for(body)
      body = JSON.parse(body)
      return body['message'] unless body['message'].to_s.empty?

      body['result'].to_s.empty? ? NO_MESSAGE_TEXT : Base64.decode64(body['result'])
    rescue JSON::ParserError
      NO_MESSAGE_TEXT
    end
  end
end