Class: JiraGateway

Inherits:
Object
  • Object
show all
Defined in:
lib/jirametrics/jira_gateway.rb

Constant Summary collapse

RETRYABLE_EXIT_CODES =
[7, 28, 35, 56].freeze
MAX_RETRIES =
3
RETRY_DELAY_SECONDS =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_system:, jira_config:, settings:) ⇒ JiraGateway

Returns a new instance of JiraGateway.



16
17
18
19
20
21
# File 'lib/jirametrics/jira_gateway.rb', line 16

def initialize file_system:, jira_config:, settings:
  @file_system = file_system
  load_jira_config(jira_config)
  @settings = settings
  @ignore_ssl_errors = settings['ignore_ssl_errors']
end

Instance Attribute Details

#file_systemObject (readonly)

Returns the value of attribute file_system.



10
11
12
# File 'lib/jirametrics/jira_gateway.rb', line 10

def file_system
  @file_system
end

#ignore_ssl_errorsObject

Returns the value of attribute ignore_ssl_errors.



9
10
11
# File 'lib/jirametrics/jira_gateway.rb', line 9

def ignore_ssl_errors
  @ignore_ssl_errors
end

#jira_urlObject (readonly)

Returns the value of attribute jira_url.



10
11
12
# File 'lib/jirametrics/jira_gateway.rb', line 10

def jira_url
  @jira_url
end

#settingsObject (readonly)

Returns the value of attribute settings.



10
11
12
# File 'lib/jirametrics/jira_gateway.rb', line 10

def settings
  @settings
end

Instance Method Details

#call_url(relative_url:) ⇒ Object



71
72
73
74
# File 'lib/jirametrics/jira_gateway.rb', line 71

def call_url relative_url:
  command = make_curl_command url: "#{@jira_url}#{relative_url}"
  exec_and_parse_response command: command, stdin_data: nil
end

#capture3(command, stdin_data:) ⇒ Object



61
62
63
64
# File 'lib/jirametrics/jira_gateway.rb', line 61

def capture3 command, stdin_data:
  # In it's own method so we can mock it out in tests
  Open3.capture3(command, stdin_data: stdin_data)
end

#cloud?Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/jirametrics/jira_gateway.rb', line 141

def cloud?
  @jira_url.downcase.end_with? '.atlassian.net'
end

#exec_and_parse_response(command:, stdin_data:) ⇒ Object



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
# File 'lib/jirametrics/jira_gateway.rb', line 28

def exec_and_parse_response command:, stdin_data:
  log_entry = "  #{command.gsub(/\s+/, ' ')}"
  log_entry = sanitize_message log_entry
  @file_system.log log_entry

  retries = 0
  loop do
    stdout, stderr, status = capture3(command, stdin_data: stdin_data)

    if status.success?
      @file_system.log "Returned (stderr): #{stderr.inspect}" unless stderr == ''
      raise 'no response from curl on stdout' if stdout == ''
      return parse_response(command: command, result: stdout)
    end

    if RETRYABLE_EXIT_CODES.include?(status.exitstatus) && retries < MAX_RETRIES
      retries += 1
      @file_system.log "Transient network error (exit #{status.exitstatus}), retrying in #{RETRY_DELAY_SECONDS}s (attempt #{retries}/#{MAX_RETRIES})..."
      sleep_between_retries
      next
    end

    @file_system.error "Failed call with exit status #{status.exitstatus}!"
    @file_system.error "Returned (stdout): #{stdout.inspect}"
    @file_system.error "Returned (stderr): #{stderr.inspect}"
    if stderr.include?('401')
      raise 'The request was not authorized. Verify that your authentication token hasn\'t expired'
    end
    raise "Failed call with exit status #{status.exitstatus}. " \
      "See #{@file_system.logfile_name} for details"
  end
end

#json_successful?(json) ⇒ Boolean

Returns:

  • (Boolean)


134
135
136
137
138
139
# File 'lib/jirametrics/jira_gateway.rb', line 134

def json_successful? json
  return false if json.is_a?(Hash) && (json['error'] || json['errorMessages'] || json['errorMessage'])
  return false if json.is_a?(Array) && json.first == 'errorMessage'

  true
end

#load_jira_config(jira_config) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/jirametrics/jira_gateway.rb', line 97

def load_jira_config jira_config
  @jira_url = jira_config['url']
  raise 'Must specify URL in config' if @jira_url.nil?

  @jira_email = jira_config['email']
  @jira_api_token = jira_config['api_token']
  @jira_personal_access_token = jira_config['personal_access_token']

  raise 'When specifying an api-token, you must also specify email' if @jira_api_token && !@jira_email

  if @jira_api_token && @jira_personal_access_token
    raise "You can't specify both an api-token and a personal-access-token. They don't work together."
  end

  @cookies = (jira_config['cookies'] || []).collect { |key, value| "#{key}=#{value}" }.join(';')
end

#make_curl_command(url:, method: 'GET') ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/jirametrics/jira_gateway.rb', line 114

def make_curl_command url:, method: 'GET'
  command = +''
  command << 'curl'
  command << ' -L' # follow redirects
  command << ' -s' # silent
  command << ' -k' if @ignore_ssl_errors # insecure
  command << " --cookie #{@cookies.inspect}" unless @cookies.empty?
  command << " --user #{@jira_email}:#{@jira_api_token}" if @jira_api_token
  command << " -H \"Authorization: Bearer #{@jira_personal_access_token}\"" if @jira_personal_access_token
  command << " --request #{method}"
  if method == 'POST'
    command << ' --data @-'
    command << ' --header "Content-Type: application/json"'
  end
  command << ' --header "Accept: application/json"'
  command << ' --show-error --fail' # Better diagnostics when the server returns an error
  command << " --url \"#{url}\""
  command
end

#parse_response(command:, result:) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/jirametrics/jira_gateway.rb', line 76

def parse_response command:, result:
  begin
    json = JSON.parse(result)
  rescue # rubocop:disable Style/RescueStandardError
    message = "Unable to parse results from #{sanitize_message(command)}"
    @file_system.error message, more: result
    raise message
  end

  raise "Download failed with: #{JSON.pretty_generate(json)}" unless json_successful?(json)

  json
end

#post_request(relative_url:, payload:) ⇒ Object



23
24
25
26
# File 'lib/jirametrics/jira_gateway.rb', line 23

def post_request relative_url:, payload:
  command = make_curl_command url: "#{@jira_url}#{relative_url}", method: 'POST'
  exec_and_parse_response command: command, stdin_data: payload
end

#sanitize_message(message) ⇒ Object



90
91
92
93
94
95
# File 'lib/jirametrics/jira_gateway.rb', line 90

def sanitize_message message
  token = @jira_api_token || @jira_personal_access_token
  return message unless token # cookie based authentication

  message.gsub(token, '[API_TOKEN]')
end

#sleep_between_retriesObject



66
67
68
69
# File 'lib/jirametrics/jira_gateway.rb', line 66

def sleep_between_retries
  # In its own method so we can mock it out in tests
  sleep RETRY_DELAY_SECONDS
end