Commit e1a167ee authored by rpereira2's avatar rpereira2

Add a Prometheus API per environment

The api will proxy requests to the environment's prometheus server.
The Prometheus::ProxyService class can be reused when we add support for
group prometheus servers.
parent 5dd6e752
# frozen_string_literal: true
class Projects::Environments::PrometheusApiController < Projects::ApplicationController
before_action :authorize_read_prometheus!
before_action :environment
def proxy
permitted = permit_params
result = Prometheus::ProxyService.new(
environment,
request.method,
permitted[:proxy_path],
permitted.except(:proxy_path) # rubocop: disable CodeReuse/ActiveRecord
).execute
if result.nil?
render status: :accepted, json: {
status: 'processing',
message: 'Not ready yet. Try again later.'
}
return
end
if result[:status] == :success
render status: result[:http_status], json: result[:body]
else
render status: result[:http_status] || :bad_request, json: {
status: result[:status],
message: result[:message]
}
end
end
private
def permit_params
params.permit([
:proxy_path, :query, :time, :timeout, :start, :end, :step, { match: [] },
:match_target, :metric, :limit
])
end
def environment
@environment ||= project.environments.find(params[:id])
end
end
......@@ -204,6 +204,7 @@ class ProjectPolicy < BasePolicy
enable :read_merge_request
enable :read_sentry_issue
enable :read_release
enable :read_prometheus
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
......
---
title: Add a Prometheus API per environment
merge_request: 26841
author:
type: added
......@@ -219,6 +219,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :metrics
get :additional_metrics
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy'
end
collection do
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::Environments::PrometheusApiController do
set(:project) { create(:project) }
set(:environment) { create(:environment, project: project) }
set(:user) { create(:user) }
before do
project.add_reporter(user)
sign_in(user)
end
describe 'GET #proxy' do
let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
let(:prometheus_response) { { status: :success, body: response_body } }
let(:json_response_body) { JSON.parse(response_body) }
let(:response_body) do
"{\"status\":\"success\",\"data\":{\"resultType\":\"scalar\",\"result\":[1553864609.117,\"1\"]}}"
end
before do
allow(Prometheus::ProxyService).to receive(:new)
.with(environment, 'GET', 'query', anything)
.and_return(prometheus_proxy_service)
allow(prometheus_proxy_service).to receive(:execute)
.and_return(prometheus_response)
end
it 'returns prometheus response' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(json_response_body)
end
it 'filters params' do
get :proxy, params: environment_params({ extra_param: 'dangerous value' })
expect(Prometheus::ProxyService).to have_received(:new)
.with(environment, 'GET', 'query', ActionController::Parameters.new({ 'query' => '1' }).permit!)
end
context 'Prometheus::ProxyService returns nil' do
before do
allow(prometheus_proxy_service).to receive(:execute)
.and_return(nil)
end
it 'returns 202 accepted' do
get :proxy, params: environment_params
expect(json_response['status']).to eq('processing')
expect(json_response['message']).to eq('Not ready yet. Try again later.')
expect(response).to have_gitlab_http_status(:accepted)
end
end
context 'Prometheus::ProxyService returns status success' do
let(:service_response) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
before do
allow(prometheus_proxy_service).to receive(:execute)
.and_return(service_response)
end
it 'returns body' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['body']).to eq('value')
end
end
context 'Prometheus::ProxyService returns status error' do
before do
allow(prometheus_proxy_service).to receive(:execute)
.and_return(service_response)
end
context 'with http_status' do
let(:service_response) do
{ http_status: :service_unavailable, status: :error, message: 'error message' }
end
it 'sets the http response status code' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(json_response['status']).to eq('error')
expect(json_response['message']).to eq('error message')
end
end
context 'without http_status' do
let(:service_response) { { status: :error, message: 'error message' } }
it 'returns message' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['status']).to eq('error')
expect(json_response['message']).to eq('error message')
end
end
end
context 'with anonymous user' do
before do
sign_out(user)
end
it 'redirects to signin page' do
get :proxy, params: environment_params
expect(response).to redirect_to(new_user_session_path)
end
end
context 'without correct permissions' do
before do
project.team.truncate
end
it 'returns 404' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
private
def environment_params(params = {})
{
id: environment.id,
namespace_id: project.namespace,
project_id: project,
proxy_path: 'query',
query: '1'
}.merge(params)
end
end
......@@ -25,6 +25,7 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code read_sentry_issue read_release
read_prometheus
]
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment