diff --git a/lib/prometheus/client/push.rb b/lib/prometheus/client/push.rb index ca556ed1..ef250df2 100644 --- a/lib/prometheus/client/push.rb +++ b/lib/prometheus/client/push.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/prometheus/client/push_spec.rb b/spec/prometheus/client/push_spec.rb index 2f0b70a5..78af05b3 100644 --- a/spec/prometheus/client/push_spec.rb +++ b/spec/prometheus/client/push_spec.rb @@ -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',