Class: ControlplaneApiDirect

Inherits:
Object
  • Object
show all
Defined in:
lib/core/controlplane_api_direct.rb

Defined Under Namespace

Classes: ForbiddenError, RedactedDebugOutput

Constant Summary collapse

API_METHODS =
{
  get: Net::HTTP::Get,
  patch: Net::HTTP::Patch,
  post: Net::HTTP::Post,
  put: Net::HTTP::Put,
  delete: Net::HTTP::Delete
}.freeze
API_HOSTS =
{ api: "https://api.cpln.io", logs: "https://logs.cpln.io" }.freeze
API_TOKEN_EXPIRY_SECONDS =
300

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.traceObject

Returns the value of attribute trace.



55
56
57
# File 'lib/core/controlplane_api_direct.rb', line 55

def trace
  @trace
end

Class Method Details

.parse_org(url) ⇒ Object

rubocop:enable Style/ClassVars



154
155
156
# File 'lib/core/controlplane_api_direct.rb', line 154

def self.parse_org(url)
  url.match(%r{^/org/([^/]+)})&.[](1)
end

.reset_api_tokenObject



149
150
151
# File 'lib/core/controlplane_api_direct.rb', line 149

def self.reset_api_token
  remove_class_variable(:@@api_token) if defined?(@@api_token)
end

Instance Method Details

#api_host(host) ⇒ Object



91
92
93
94
95
96
97
98
# File 'lib/core/controlplane_api_direct.rb', line 91

def api_host(host)
  case host
  when :api
    ENV.fetch("CPLN_ENDPOINT", API_HOSTS[host])
  else
    API_HOSTS[host]
  end
end

#api_tokenObject

rubocop:disable Style/ClassVars



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/core/controlplane_api_direct.rb', line 108

def api_token # rubocop:disable Metrics/MethodLength
  return @@api_token if defined?(@@api_token)

  @@api_token = {
    token: ENV.fetch("CPLN_TOKEN", nil),
    comes_from_profile: false
  }
  if @@api_token[:token].nil?
    @@api_token = {
      token: Shell.cmd("cpln", "profile", "token")[:output].chomp,
      comes_from_profile: true
    }
  end
  token = @@api_token[:token]
  # Allow any token that does not contain line breaks. Scoped service-account
  # tokens include punctuation such as '/', '+', ':', and '=', so format
  # validation is deferred to the Control Plane API.
  return @@api_token if token && !token.empty? && !token.match?(/[\r\n]/)

  raise "Unknown API token format. " \
        "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
end

#authorization_headerObject



100
101
102
103
104
105
# File 'lib/core/controlplane_api_direct.rb', line 100

def authorization_header
  token = api_token[:token]
  return token if token.match?(/\ABearer\s+/i)

  "Bearer #{token}"
end

#call(url, method:, host: :api, body: nil) ⇒ Object

rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity



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
# File 'lib/core/controlplane_api_direct.rb', line 58

def call(url, method:, host: :api, body: nil) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
  trace = ControlplaneApiDirect.trace
  uri = URI("#{api_host(host)}#{url}")
  request = API_METHODS[method].new(uri)
  request["Content-Type"] = "application/json"

  refresh_api_token if should_refresh_api_token?

  request["Authorization"] = authorization_header
  request.body = body.to_json if body

  Shell.debug(method.upcase, "#{uri} #{body&.to_json}")

  http = Net::HTTP.new(uri.hostname, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.set_debug_output(RedactedDebugOutput.new) if trace

  response = http.start { |ht| ht.request(request) }

  case response
  when Net::HTTPOK
    JSON.parse(response.body)
  when Net::HTTPAccepted
    true
  when Net::HTTPNotFound
    nil
  when Net::HTTPForbidden
    raise ForbiddenError.new(url: url, response: response)
  else
    raise("#{response} #{response.body}")
  end
end

#refresh_api_tokenObject



145
146
147
# File 'lib/core/controlplane_api_direct.rb', line 145

def refresh_api_token
  @@api_token[:token] = Shell.cmd("cpln", "profile", "token")[:output].chomp
end

#should_refresh_api_token?Boolean

Returns ‘true` when the token is about to expire in 5 minutes

Returns:

  • (Boolean)


132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/core/controlplane_api_direct.rb', line 132

def should_refresh_api_token?
  return false unless api_token[:comes_from_profile]

  payload, = JWT.decode(api_token[:token], nil, false, algorithms: [])
  return false unless payload.is_a?(Hash) && payload["exp"]

  difference_in_seconds = payload["exp"].to_i - Time.now.to_i

  difference_in_seconds <= API_TOKEN_EXPIRY_SECONDS
rescue JWT::DecodeError
  false
end