Commit 7845dbcf authored by Reuben Pereira's avatar Reuben Pereira Committed by Thong Kuah

Create new Prometheus::ProxyVariableSubstitutionService

Create new service to do template variable replacement on the query
parameter of the Prometheus proxy API.
parent 25b54e73
...@@ -7,23 +7,34 @@ class Projects::Environments::PrometheusApiController < Projects::ApplicationCon ...@@ -7,23 +7,34 @@ class Projects::Environments::PrometheusApiController < Projects::ApplicationCon
before_action :environment before_action :environment
def proxy def proxy
result = Prometheus::ProxyService.new( variable_substitution_result =
variable_substitution_service.new(environment, permit_params).execute
if variable_substitution_result[:status] == :error
return error_response(variable_substitution_result)
end
prometheus_result = Prometheus::ProxyService.new(
environment, environment,
proxy_method, proxy_method,
proxy_path, proxy_path,
proxy_params variable_substitution_result[:params]
).execute ).execute
return continue_polling_response if result.nil? return continue_polling_response if prometheus_result.nil?
return error_response(result) if result[:status] == :error return error_response(prometheus_result) if prometheus_result[:status] == :error
success_response(result) success_response(prometheus_result)
end end
private private
def query_context def variable_substitution_service
Gitlab::Prometheus::QueryVariables.call(environment) Prometheus::ProxyVariableSubstitutionService
end
def permit_params
params.permit!
end end
def environment def environment
...@@ -37,15 +48,4 @@ class Projects::Environments::PrometheusApiController < Projects::ApplicationCon ...@@ -37,15 +48,4 @@ class Projects::Environments::PrometheusApiController < Projects::ApplicationCon
def proxy_path def proxy_path
params[:proxy_path] params[:proxy_path]
end end
def proxy_params
substitute_query_variables(params).permit!
end
def substitute_query_variables(params)
query = params[:query]
return params unless query
params.merge(query: query % query_context)
end
end end
# frozen_string_literal: true
module Prometheus
class ProxyVariableSubstitutionService < BaseService
include Stepable
steps :add_params_to_result, :substitute_ruby_variables
def initialize(environment, params = {})
@environment, @params = environment, params.deep_dup
end
def execute
execute_steps
end
private
def add_params_to_result(result)
result[:params] = params
success(result)
end
def substitute_ruby_variables(result)
return success(result) unless query
# The % operator doesn't replace variables if the hash contains string
# keys.
result[:params][:query] = query % predefined_context.symbolize_keys
success(result)
rescue TypeError, ArgumentError => exception
log_error(exception.message)
Gitlab::Sentry.track_acceptable_exception(exception, extra: {
template_string: query,
variables: predefined_context
})
error(_('Malformed string'))
end
def predefined_context
@predefined_context ||= Gitlab::Prometheus::QueryVariables.call(@environment)
end
def query
params[:query]
end
end
end
...@@ -10614,6 +10614,9 @@ msgstr "" ...@@ -10614,6 +10614,9 @@ msgstr ""
msgid "Makes this issue confidential." msgid "Makes this issue confidential."
msgstr "" msgstr ""
msgid "Malformed string"
msgstr ""
msgid "Manage" msgid "Manage"
msgstr "" msgstr ""
......
...@@ -63,9 +63,7 @@ describe Projects::Environments::PrometheusApiController do ...@@ -63,9 +63,7 @@ describe Projects::Environments::PrometheusApiController do
context 'with nil query' do context 'with nil query' do
let(:params_without_query) do let(:params_without_query) do
params = environment_params environment_params.except(:query)
params.delete(:query)
params
end end
before do before do
......
...@@ -14,7 +14,7 @@ describe Gitlab::Prometheus::QueryVariables do ...@@ -14,7 +14,7 @@ describe Gitlab::Prometheus::QueryVariables do
it do it do
is_expected.to include(environment_filter: is_expected.to include(environment_filter:
%{container_name!="POD",environment="#{slug}"}) %Q[container_name!="POD",environment="#{slug}"])
end end
context 'without deployment platform' do context 'without deployment platform' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Prometheus::ProxyVariableSubstitutionService do
describe '#execute' do
let_it_be(:environment) { create(:environment) }
let(:params_keys) { { query: 'up{environment="%{ci_environment_slug}"}' } }
let(:params) { ActionController::Parameters.new(params_keys).permit! }
let(:result) { subject.execute }
subject { described_class.new(environment, params) }
shared_examples 'success' do
it 'replaces variables with values' do
expect(result[:status]).to eq(:success)
expect(result[:params][:query]).to eq(expected_query)
end
end
shared_examples 'error' do |message|
it 'returns error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq(message)
end
end
context 'does not alter params passed to the service' do
it do
subject.execute
expect(params).to eq(
ActionController::Parameters.new(
query: 'up{environment="%{ci_environment_slug}"}'
).permit!
)
end
end
context 'with predefined variables' do
it_behaves_like 'success' do
let(:expected_query) { %Q[up{environment="#{environment.slug}"}] }
end
context 'with nil query' do
let(:params_keys) { {} }
it_behaves_like 'success' do
let(:expected_query) { nil }
end
end
end
context 'ruby template rendering' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},%{environment_filter}}' }
end
it_behaves_like 'success' do
let(:expected_query) do
"up{env=#{environment.slug},container_name!=\"POD\"," \
"environment=\"#{environment.slug}\"}"
end
end
context 'with multiple occurrences of variable in string' do
let(:params_keys) do
{ query: 'up{env1=%{ci_environment_slug},env2=%{ci_environment_slug}}' }
end
it_behaves_like 'success' do
let(:expected_query) { "up{env1=#{environment.slug},env2=#{environment.slug}}" }
end
end
context 'with multiple variables in string' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},%{environment_filter}}' }
end
it_behaves_like 'success' do
let(:expected_query) do
"up{env=#{environment.slug}," \
"container_name!=\"POD\",environment=\"#{environment.slug}\"}"
end
end
end
context 'with unknown variables in string' do
let(:params_keys) { { query: 'up{env=%{env_slug}}' } }
it_behaves_like 'success' do
let(:expected_query) { 'up{env=%{env_slug}}' }
end
end
# This spec is needed if there are multiple keys in the context provided
# by `Gitlab::Prometheus::QueryVariables.call(environment)` which is
# passed to the Ruby `%` operator.
# If the number of keys in the context is one, there is no need for
# this spec.
context 'with extra variables in context' do
let(:params_keys) { { query: 'up{env=%{ci_environment_slug}}' } }
it_behaves_like 'success' do
let(:expected_query) { "up{env=#{environment.slug}}" }
end
it 'has more than one variable in context' do
expect(Gitlab::Prometheus::QueryVariables.call(environment).size).to be > 1
end
end
# The ruby % operator will not replace known variables if there are unknown
# variables also in the string. It doesn't raise an error
# (though the `sprintf` and `format` methods do).
context 'with unknown and known variables in string' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},other_env=%{env_slug}}' }
end
it_behaves_like 'success' do
let(:expected_query) { 'up{env=%{ci_environment_slug},other_env=%{env_slug}}' }
end
end
context 'when rendering raises error' do
context 'when TypeError is raised' do
let(:params_keys) { { query: '{% a %}' } }
it_behaves_like 'error', 'Malformed string'
end
context 'when ArgumentError is raised' do
let(:params_keys) { { query: '%<' } }
it_behaves_like 'error', 'Malformed string'
end
end
end
end
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