Class: Jets::Api::Client

Inherits:
Object
  • Object
show all
Extended by:
Memoist
Includes:
Error::Handlers, Util::Logging
Defined in:
lib/jets/api/client.rb

Constant Summary collapse

NETWORK_ERRORS =
[
  Errno::ECONNREFUSED,
  Errno::ECONNRESET,
  Errno::EHOSTUNREACH,
  Errno::ETIMEDOUT,
  Jets::Api::Error::Maintenance,
  Jets::Api::Error::ServiceUnavailable, # mimic 503 Net::HTTPServiceUnavailable
  Net::HTTPServiceUnavailable, # cannot rescue. Unsure why
  Net::OpenTimeout,
  Net::ReadTimeout,
  OpenSSL::SSL::SSLError,
  OpenURI::HTTPError,
  SocketError
]
@@max_retries =

4 attempts total

3

Instance Method Summary collapse

Methods included from Util::Logging

#log

Methods included from Error::Handlers

#general_api_error, #handle_as_error?, #handle_error_response!, #specific_api_error

Instance Method Details

#accountObject



205
206
207
208
209
# File 'lib/jets/api/client.rb', line 205

def 
  sts.get_caller_identity.
rescue
  nil
end

#api_keyObject



181
182
183
# File 'lib/jets/api/client.rb', line 181

def api_key
  Jets::Api.api_key
end

#build_request(klass, url, data = {}, headers = {}) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/jets/api/client.rb', line 34

def build_request(klass, url, data = {}, headers = {})
  req = klass.new(url) # url includes query string and uri.path does not, must used url
  set_headers!(req)
  if [Net::HTTP::Delete, Net::HTTP::Patch, Net::HTTP::Post, Net::HTTP::Put].include?(klass)
    text = JSON.dump(data)
    puts_debug_request(data)
    req.body = text
    req.content_length = text.bytesize
    req.content_type = "application/json"
  end
  req
end

#commandObject



136
137
138
139
140
141
142
143
# File 'lib/jets/api/client.rb', line 136

def command
  args = ARGV.reject { |arg| arg.include?("-") }
  if args.first == "rollback" # IE: jets rollback 8
    args.first
  else
    args.join(":")
  end
end

#delete(path, data = {}) ⇒ Object



201
202
203
# File 'lib/jets/api/client.rb', line 201

def delete(path, data = {})
  execute_request(Net::HTTP::Delete, path, data)
end

#endpointObject



217
218
219
220
221
222
223
224
225
226
# File 'lib/jets/api/client.rb', line 217

def endpoint
  return ENV["JETS_API"] if ENV["JETS_API"]

  major = Jets::VERSION.split(".").first.to_i
  if major >= 6
    "https://api.rubyonjets.com/v2"
  else
    "https://api.rubyonjets.com/v1"
  end
end

#execute_request(klass, path, data = {}, headers = {}) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/jets/api/client.rb', line 12

def execute_request(klass, path, data = {}, headers = {})
  data = global_params(path).merge(data)
  if klass == Net::HTTP::Get
    path = path_with_query(path, data)
    data = {}
  end

  url = url(path)
  req = build_request(klass, url, data, headers)
  http_resp = http_request(req)

  resp = Jets::Api::Response.new(http_resp)
  puts_debug_resp(url, resp)

  if handle_as_error?(resp.http_status)
    handle_error_response!(resp)
  end

  # Always translate Json Response to Ruby Hash
  resp.data # JSON.parse(@http_resp.body) => Ruby hash
end

#get(path, data = {}) ⇒ Object



185
186
187
# File 'lib/jets/api/client.rb', line 185

def get(path, data = {})
  execute_request(Net::HTTP::Get, path, data)
end

#global_params(path) ⇒ Object



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

def global_params(path)
  args = ARGV.reject { |arg| arg.include?("-") }
  params = {
    account: Jets.aws.,
    command: command,
    jets_env: Jets.env.to_s,
    jets_extra: Jets.extra,
    jets_go_version: ENV["JETS_GO_VERSION"],
    jets_remote_version: ENV["JETS_REMOTE_VERSION"],
    jets_version: Jets::VERSION,
    region: Jets.aws.region,
    ruby_version: RUBY_VERSION
  }
  if Jets::Thor::ProjectCheck.new(args).project? || command == "delete"
    params[:project_namespace] = Jets.project.namespace
    params[:project_name] = Jets.project.name
  end

  params.delete_if { |k, v| v.nil? }
  params
end

#httpObject



105
106
107
108
109
110
111
# File 'lib/jets/api/client.rb', line 105

def http
  uri = URI(endpoint)
  http = Net::HTTP.new(uri.host, uri.port)
  http.open_timeout = http.read_timeout = 30
  http.use_ssl = true if uri.scheme == "https"
  http
end

#http_request(req, retries = 0) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/jets/api/client.rb', line 62

def http_request(req, retries = 0)
  resp = http.request(req) # send request. returns raw response
  reraise_error_from_503!(req, resp)
  resp
rescue *NETWORK_ERRORS => error
  if retries < @@max_retries
    delay = 2**retries
    log.debug "Error: #{error.class} #{error.message} retrying after #{delay} seconds..."
    sleep delay
    retries += 1
    retry
  elsif error.is_a?(Jets::Api::Error::Maintenance)
    # The Jets API is under maintenance
    log.info error.message # message provides context already
    exit 1
  else
    message = "Unexpected error #{error.class.name} communicating with the Jets API. "
    message += " Request was attempted #{retries + 1} times."
    raise Jets::Api::Error::Connection, message + "\nNetwork error: #{error.message}"
  end
end

#maintenance_mode?(body) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
# File 'lib/jets/api/client.rb', line 98

def maintenance_mode?(body)
  payload = JSON.parse(body)
  payload["status"] == "maintenance"
rescue JSON::ParserError
  false
end

#patch(path, data = {}) ⇒ Object



197
198
199
# File 'lib/jets/api/client.rb', line 197

def patch(path, data = {})
  execute_request(Net::HTTP::Patch, path, data)
end

#path_with_query(path, query = {}) ⇒ Object



151
152
153
154
155
# File 'lib/jets/api/client.rb', line 151

def path_with_query(path, query = {})
  return path if query.empty?
  separator = path.include?("?") ? "&" : "?"
  "#{path}#{separator}#{query.to_query}"
end

#post(path, data = {}) ⇒ Object



189
190
191
# File 'lib/jets/api/client.rb', line 189

def post(path, data = {})
  execute_request(Net::HTTP::Post, path, data)
end

#processable?(http_code) ⇒ Boolean

422 Unprocessable Entity: Server understands the content type of the request entity, and the syntax of the request entity is correct, but it was unable to process the contained instructions. TODO: remove? or rename to ha

Returns:

  • (Boolean)


169
170
171
# File 'lib/jets/api/client.rb', line 169

def processable?(http_code)
  http_code =~ /^2/ || http_code =~ /^4/
end

#put(path, data = {}) ⇒ Object



193
194
195
# File 'lib/jets/api/client.rb', line 193

def put(path, data = {})
  execute_request(Net::HTTP::Put, path, data)
end

#puts_debug_request(data) ⇒ Object



240
241
242
243
244
# File 'lib/jets/api/client.rb', line 240

def puts_debug_request(data)
  return unless ENV["JETS_DEBUG_API"]
  log.info "POST data:"
  log.info JSON.pretty_generate(data)
end

#puts_debug_resp(url, resp) ⇒ Object



229
230
231
232
233
234
235
236
237
238
# File 'lib/jets/api/client.rb', line 229

def puts_debug_resp(url, resp)
  return unless ENV["JETS_DEBUG_API"]
  puts "API Response for url #{url}"
  begin
    puts JSON.pretty_generate(resp.data)
  rescue
    puts "Cannot JSON pretty_generate resp #{resp.inspect}"
    nil
  end
end

#reraise_error_from_503!(req, resp) ⇒ Object

For some reason, rescue Net::HTTPServiceUnavailable is not being caught. So mimic it. Can reproduce by using local Jets API service and not starting it up



87
88
89
90
91
92
93
94
95
96
# File 'lib/jets/api/client.rb', line 87

def reraise_error_from_503!(req, resp)
  return unless resp.code == "503" # Service Unavailable

  if maintenance_mode?(resp.body)
    payload = JSON.parse(resp.body)
    raise Jets::Api::Error::Maintenance, payload["message"]
  else
    raise Jets::Api::Error::ServiceUnavailable, "Request #{req.path}"
  end
end

#sessionObject



173
174
175
176
177
178
179
# File 'lib/jets/api/client.rb', line 173

def session
  session_path = "#{ENV["HOME"]}/.jets/session.yml"
  if File.exist?(session_path)
    data = YAML.load_file(session_path)
    data["secret_token"]
  end
end

#set_headers!(req, headers = {}) ⇒ Object



157
158
159
160
161
162
163
# File 'lib/jets/api/client.rb', line 157

def set_headers!(req, headers = {})
  headers.each { |k, v| req[k] = v }
  req["Authorization"] = api_key if api_key
  req["x-account"] =  if 
  req["x-session"] = session if session
  req
end

#stsObject



212
213
214
# File 'lib/jets/api/client.rb', line 212

def sts
  Aws::STS::Client.new
end

#url(path) ⇒ Object

API does not include the /. IE: app.terraform.io/api/v2



146
147
148
149
# File 'lib/jets/api/client.rb', line 146

def url(path)
  path = "/#{path}" unless path.starts_with?("/")
  "#{endpoint}#{path}"
end