Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions lib/prometheus/client/push.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ def parse(url)
def build_path(job, grouping_key)
job = job.to_s

# Job can't be empty, but it can contain `/`, so we need to base64
# encode it in that case
if job.include?('/')
# Job can't be empty, but it can contain `/` or be a dot segment (`.`
# or `..`), so we need to base64 encode it in that case
if needs_base64?(job)
encoded_job = Base64.urlsafe_encode64(job)
path = "#{PATH}/job@base64/#{encoded_job}"
else
Expand All @@ -101,7 +101,7 @@ def build_path(job, grouping_key)
grouping_key.each do |label, value|
value = value.to_s

if value.include?('/')
if needs_base64?(value)
encoded_value = Base64.urlsafe_encode64(value)
path += "/#{label}@base64/#{encoded_value}"
# While it's valid for the urlsafe_encode64 function to return an
Expand All @@ -126,6 +126,14 @@ def build_path(job, grouping_key)
path
end

# A `/` would split the value into several path segments, and `.` or
# `..` are dot segments that HTTP libraries, proxies and the
# Pushgateway itself normalise away (so the request would end up at a
# different path). The Pushgateway docs ask for base64 in both cases.
def needs_base64?(value)
value.include?('/') || value == '.' || value == '..'
end

def request(req_class, registry = nil)
validate_no_label_clashes!(registry) if registry

Expand Down
16 changes: 16 additions & 0 deletions spec/prometheus/client/push_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@
expect(push.path).to eql('/metrics/job/test-job/foo@base64/YmFyL2Jheg==')
end

it 'encodes the job name in url-safe base64 if it is a dot segment' do
expect(Prometheus::Client::Push.new(job: '.').path)
.to eql('/metrics/job@base64/Lg==')
expect(Prometheus::Client::Push.new(job: '..').path)
.to eql('/metrics/job@base64/Li4=')
end

it 'encodes grouping key label values that are dot segments in url-safe base64' do
push = Prometheus::Client::Push.new(
job: 'test-job',
grouping_key: { foo: ".", bar: "..", baz: "..." },
)

expect(push.path).to eql('/metrics/job/test-job/foo@base64/Lg==/bar@base64/Li4=/baz/...')
end

it 'encodes empty grouping key label values as a single base64 padding character' do
push = Prometheus::Client::Push.new(
job: 'test-job',
Expand Down