gitlab-exporter 17.2.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +5 -0
- data/Gemfile.lock +23 -10
- data/README.md +7 -0
- data/config/gitlab-exporter.yml.example +8 -0
- data/gitlab-exporter.gemspec +5 -3
- data/lib/gitlab_exporter/sidekiq.rb +74 -15
- data/lib/gitlab_exporter/version.rb +1 -1
- data/spec/integration/sidekiq_credentials_provider_spec.rb +66 -0
- data/spec/integration/sidekiq_future_sets_spec.rb +35 -0
- data/spec/sidekiq_spec.rb +67 -0
- metadata +57 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eeb80abff6288b4494ab0633c290b45d97398f27e9d45c66efeb7a5e712d1ae4
|
|
4
|
+
data.tar.gz: 24e9c2d111a136700998c2a853fb09410f80f5385bf40dad240c80f9e707130c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a33dbd9cb98fa81c22228a74d416e9ee46ee9cb71a27ae3e9ff81f428032ee11c7e6baa4d88109a85735a347b373962c62ad00bfee79e0a5e97a8efb6d020b84
|
|
7
|
+
data.tar.gz: 3d70ed949a88f0fbde889333231dc0c4072d6602970ebec0eb6f6c8568b762d9af155df5e398c748f8532474fd5456649b4b6bf1cdaef6cb3bc0dd4a656a0213
|
data/.gitlab-ci.yml
CHANGED
|
@@ -62,6 +62,9 @@ rspec_integration:
|
|
|
62
62
|
services:
|
|
63
63
|
- name: redis:${REDIS_VERSION}
|
|
64
64
|
alias: redis-master
|
|
65
|
+
- name: redis:${REDIS_VERSION}
|
|
66
|
+
alias: redis-auth
|
|
67
|
+
command: ["redis-server", "--requirepass", "test-token-secret"]
|
|
65
68
|
- name: bitnamilegacy/redis-sentinel:${REDIS_SENTINEL_VERSION}
|
|
66
69
|
alias: redis-sentinel
|
|
67
70
|
command:
|
|
@@ -82,6 +85,8 @@ rspec_integration:
|
|
|
82
85
|
REDIS_URL: "redis://redis"
|
|
83
86
|
REDIS_SENTINEL_URL: "redis://mymaster"
|
|
84
87
|
REDIS_SENTINELS: "redis-sentinel:26379"
|
|
88
|
+
REDIS_AUTH_URL: "redis://redis-auth:6379"
|
|
89
|
+
REDIS_AUTH_PASSWORD: "test-token-secret"
|
|
85
90
|
REDIS_VERSION: latest
|
|
86
91
|
REDIS_SENTINEL_VERSION: latest
|
|
87
92
|
REDIS_SENTINEL_USERNAME: "my-sentinel-username"
|
data/Gemfile.lock
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
gitlab-exporter (
|
|
4
|
+
gitlab-exporter (18.0.0)
|
|
5
|
+
az-identity (~> 0.1)
|
|
5
6
|
base64 (= 0.3.0)
|
|
6
7
|
connection_pool (= 2.5.5)
|
|
7
8
|
deep_merge (~> 1.2.2)
|
|
8
9
|
faraday (= 2.15.1)
|
|
9
10
|
pg (= 1.6.3)
|
|
10
11
|
quantile (= 0.2.1)
|
|
11
|
-
redis (
|
|
12
|
-
redis-
|
|
13
|
-
|
|
12
|
+
redis (>= 5, < 6)
|
|
13
|
+
redis-cloud-auth (~> 0.1)
|
|
14
|
+
redis-namespace (~> 1.11)
|
|
15
|
+
sidekiq (>= 7, < 8)
|
|
14
16
|
webrick (~> 1.9)
|
|
15
17
|
|
|
16
18
|
GEM
|
|
17
19
|
remote: https://rubygems.org/
|
|
18
20
|
specs:
|
|
19
21
|
ast (2.4.3)
|
|
22
|
+
az-identity (0.1.0)
|
|
23
|
+
base64
|
|
24
|
+
faraday (~> 2.0)
|
|
20
25
|
base64 (0.3.0)
|
|
21
26
|
connection_pool (2.5.5)
|
|
22
27
|
deep_merge (1.2.2)
|
|
@@ -38,9 +43,15 @@ GEM
|
|
|
38
43
|
prism (1.9.0)
|
|
39
44
|
quantile (0.2.1)
|
|
40
45
|
racc (1.8.1)
|
|
41
|
-
rack (2.2.
|
|
46
|
+
rack (2.2.25)
|
|
42
47
|
rainbow (3.1.1)
|
|
43
|
-
redis (4.
|
|
48
|
+
redis (5.4.1)
|
|
49
|
+
redis-client (>= 0.22.0)
|
|
50
|
+
redis-client (0.31.0)
|
|
51
|
+
connection_pool
|
|
52
|
+
redis-cloud-auth (0.1.1)
|
|
53
|
+
logger
|
|
54
|
+
redis-client (>= 0.14)
|
|
44
55
|
redis-namespace (1.11.0)
|
|
45
56
|
redis (>= 4)
|
|
46
57
|
regexp_parser (2.13.1)
|
|
@@ -72,10 +83,12 @@ GEM
|
|
|
72
83
|
parser (>= 3.3.7.2)
|
|
73
84
|
prism (~> 1.7)
|
|
74
85
|
ruby-progressbar (1.13.0)
|
|
75
|
-
sidekiq (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
sidekiq (7.3.10)
|
|
87
|
+
base64
|
|
88
|
+
connection_pool (>= 2.3.0, < 3)
|
|
89
|
+
logger
|
|
90
|
+
rack (>= 2.2.4, < 3.3)
|
|
91
|
+
redis-client (>= 0.23.0, < 1)
|
|
79
92
|
unicode-display_width (3.1.4)
|
|
80
93
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
81
94
|
unicode-emoji (4.2.0)
|
data/README.md
CHANGED
|
@@ -11,6 +11,13 @@ gitlab-exporter is a [Prometheus Web exporter] that does the following:
|
|
|
11
11
|
A central Prometheus process is configured to poll exporters at a specified
|
|
12
12
|
frequency.
|
|
13
13
|
|
|
14
|
+
### Requirements
|
|
15
|
+
|
|
16
|
+
The Sidekiq probe uses Sidekiq 7, which talks to Redis through `redis-client`
|
|
17
|
+
and requires a **Redis server version of 6.2 or newer**. This raises the
|
|
18
|
+
minimum Redis version from the 4.0+ that Sidekiq 6 supported; Redis servers
|
|
19
|
+
older than 6.2 are no longer supported.
|
|
20
|
+
|
|
14
21
|
### Supported Probes
|
|
15
22
|
|
|
16
23
|
Below is a list of probes added by this exporter, and their corresponding
|
|
@@ -106,6 +106,14 @@ probes:
|
|
|
106
106
|
# Uncomment if username or password is not part of the redis_url specified above
|
|
107
107
|
# redis_username: 'redis-username'
|
|
108
108
|
# redis_password: 'redis-password'
|
|
109
|
+
# Uncomment to authenticate with a short-lived, rotating token (for
|
|
110
|
+
# example Microsoft Entra on Azure Managed Redis) instead of a static
|
|
111
|
+
# password. redis_username is the AUTH username (the identity object ID);
|
|
112
|
+
# the token is fetched and refreshed automatically, so omit redis_password.
|
|
113
|
+
# redis_username: '<identity-object-id>'
|
|
114
|
+
# redis_credentials_provider:
|
|
115
|
+
# provider: azure
|
|
116
|
+
# resource: 'https://redis.azure.com'
|
|
109
117
|
redis_enable_client: true
|
|
110
118
|
# Uncomment if Redis Sentinels are needed
|
|
111
119
|
# redis_sentinels:
|
data/gitlab-exporter.gemspec
CHANGED
|
@@ -25,15 +25,17 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
# base64 is required by sidekiq but not declared by it, and is no longer a
|
|
26
26
|
# default gem as of Ruby 3.4. It used to be pulled in transitively via
|
|
27
27
|
# sinatra -> rack-protection; declare it directly now that those are gone.
|
|
28
|
+
s.add_runtime_dependency "az-identity", "~> 0.1"
|
|
28
29
|
s.add_runtime_dependency "base64", "0.3.0"
|
|
29
30
|
s.add_runtime_dependency "connection_pool", "2.5.5"
|
|
30
31
|
s.add_runtime_dependency "deep_merge", "~> 1.2.2"
|
|
31
32
|
s.add_runtime_dependency "faraday", "2.15.1"
|
|
32
33
|
s.add_runtime_dependency "pg", "1.6.3"
|
|
33
34
|
s.add_runtime_dependency "quantile", "0.2.1"
|
|
34
|
-
s.add_runtime_dependency "redis", "
|
|
35
|
-
s.add_runtime_dependency "redis-
|
|
36
|
-
s.add_runtime_dependency "
|
|
35
|
+
s.add_runtime_dependency "redis", ">= 5", "< 6"
|
|
36
|
+
s.add_runtime_dependency "redis-cloud-auth", "~> 0.1"
|
|
37
|
+
s.add_runtime_dependency "redis-namespace", "~> 1.11"
|
|
38
|
+
s.add_runtime_dependency "sidekiq", ">= 7", "< 8"
|
|
37
39
|
s.add_runtime_dependency "webrick", "~> 1.9"
|
|
38
40
|
|
|
39
41
|
s.add_development_dependency "rspec", "~> 3.12.0"
|
|
@@ -29,12 +29,19 @@ module GitLab
|
|
|
29
29
|
PrometheusMetrics.describe("sidekiq_enqueued_jobs",
|
|
30
30
|
"Total number of jobs enqueued by class name. Only inspects the first #{PROBE_JOBS_LIMIT} jobs per queue.") # rubocop:disable Layout/LineLength
|
|
31
31
|
|
|
32
|
+
# Sidekiq 7 memoizes a single Redis pool on the global config, so
|
|
33
|
+
# config.redis= cannot switch targets once the pool is built. Keep a
|
|
34
|
+
# pool per target here and run each probe through Sidekiq::Client.via.
|
|
32
35
|
def self.connection_pool
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
@connection_pool ||= {}
|
|
37
|
+
end
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
def self.credentials_provider_registered?
|
|
40
|
+
@credentials_provider_registered
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.credentials_provider_registered!
|
|
44
|
+
@credentials_provider_registered = true
|
|
38
45
|
end
|
|
39
46
|
|
|
40
47
|
def initialize(metrics: PrometheusMetrics.new, logger: nil, **opts)
|
|
@@ -89,7 +96,11 @@ module GitLab
|
|
|
89
96
|
# Default to 0; if all jobs are due in the future, there is no "negative" delay.
|
|
90
97
|
delay = 0
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
# redis-client cannot serialize the keyword-argument form
|
|
100
|
+
# (limit:/withscores:), so issue the raw command. RESP3 parses
|
|
101
|
+
# WITHSCORES into [[member, score]] with a Float score.
|
|
102
|
+
_job, timestamp = conn.call("ZRANGEBYSCORE", set, "-inf", now.to_s,
|
|
103
|
+
"LIMIT", 0, 1, "WITHSCORES").first
|
|
93
104
|
delay = now - timestamp if timestamp
|
|
94
105
|
|
|
95
106
|
@metrics.add("sidekiq_#{set}_set_processing_delay_seconds", delay)
|
|
@@ -135,7 +146,9 @@ module GitLab
|
|
|
135
146
|
worker_stats = Hash.new(0)
|
|
136
147
|
|
|
137
148
|
Sidekiq::Workers.new.map do |_pid, _tid, work|
|
|
138
|
-
|
|
149
|
+
# Sidekiq 7 wraps the work hash in Sidekiq::Work and deprecates the
|
|
150
|
+
# Hash accessor; use #job when present, fall back for older Sidekiq.
|
|
151
|
+
job_klass = work.respond_to?(:job) ? work.job.klass : work["payload"]["class"]
|
|
139
152
|
|
|
140
153
|
worker_stats[job_klass] += 1
|
|
141
154
|
end
|
|
@@ -183,33 +196,78 @@ module GitLab
|
|
|
183
196
|
|
|
184
197
|
def with_sidekiq
|
|
185
198
|
SIDEKIQ_REDIS_LOCK.synchronize {
|
|
186
|
-
Sidekiq.
|
|
187
|
-
|
|
199
|
+
# Sidekiq::Client.via sets a thread-local pool that Sidekiq.redis,
|
|
200
|
+
# Stats and Queue all read first, so each prober talks to its own
|
|
201
|
+
# Redis instead of the one that happened to build the global pool.
|
|
202
|
+
Sidekiq::Client.via(pool) do
|
|
203
|
+
return unless connected?
|
|
204
|
+
|
|
205
|
+
yield
|
|
188
206
|
end
|
|
207
|
+
}
|
|
208
|
+
end
|
|
189
209
|
|
|
190
|
-
|
|
210
|
+
# Build and cache one pool per Redis target. The cache key omits the
|
|
211
|
+
# token :password, which is a fresh callable on every call; the re-auth
|
|
212
|
+
# middleware refreshes the token per connection.
|
|
213
|
+
def pool
|
|
214
|
+
options = redis_options
|
|
215
|
+
key = options.reject { |k, _| k == :password }
|
|
191
216
|
|
|
192
|
-
|
|
193
|
-
|
|
217
|
+
self.class.connection_pool[key] ||=
|
|
218
|
+
Sidekiq::RedisConnection.create(options.merge(pool_timeout: POOL_TIMEOUT, size: POOL_SIZE))
|
|
194
219
|
end
|
|
195
220
|
|
|
196
221
|
def redis_options
|
|
197
222
|
options = {
|
|
198
223
|
url: @opts[:redis_url],
|
|
199
|
-
sentinels: redis_sentinel_options,
|
|
200
224
|
connect_timeout: 1,
|
|
201
225
|
reconnect_attempts: 0
|
|
202
226
|
}
|
|
203
227
|
|
|
228
|
+
# redis-client treats a `sentinels` key as Sentinel mode, so only set it
|
|
229
|
+
# when sentinels are actually configured.
|
|
230
|
+
sentinels = redis_sentinel_options
|
|
231
|
+
options[:sentinels] = sentinels if sentinels.is_a?(Array) && sentinels.any?
|
|
232
|
+
|
|
204
233
|
%i[username password].each do |credential|
|
|
205
234
|
options[credential] = @opts[:"redis_#{credential}"] if @opts.key?(:"redis_#{credential}")
|
|
206
235
|
end
|
|
207
236
|
|
|
208
237
|
options[:ssl_params] = parse_ssl_params(@opts[:redis_ssl_params]) if @opts.key?(:redis_ssl_params)
|
|
209
238
|
options[:id] = nil unless redis_enable_client?
|
|
239
|
+
configure_credentials_provider(options)
|
|
210
240
|
options
|
|
211
241
|
end
|
|
212
242
|
|
|
243
|
+
# Authenticate with a short-lived, rotating token (for example Microsoft
|
|
244
|
+
# Entra) instead of a static password when a credentials_provider is set.
|
|
245
|
+
# Sets a callable token password and the middleware descriptor, which
|
|
246
|
+
# Sidekiq passes through to redis-client.
|
|
247
|
+
def configure_credentials_provider(options)
|
|
248
|
+
provider = @opts[:redis_credentials_provider]
|
|
249
|
+
return unless provider
|
|
250
|
+
|
|
251
|
+
require "redis_cloud_auth"
|
|
252
|
+
register_credentials_provider_middleware!
|
|
253
|
+
|
|
254
|
+
descriptor = provider.transform_keys(&:to_sym)
|
|
255
|
+
descriptor[:username] ||= @opts[:redis_username] if @opts[:redis_username]
|
|
256
|
+
|
|
257
|
+
options[:password] = RedisCloudAuth.credential(descriptor)
|
|
258
|
+
options[:custom] = { credentials_provider: descriptor }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# RedisClient.register appends on each call, so register the re-auth
|
|
262
|
+
# middleware only once per process.
|
|
263
|
+
def register_credentials_provider_middleware!
|
|
264
|
+
return if self.class.credentials_provider_registered?
|
|
265
|
+
|
|
266
|
+
RedisCloudAuth.logger = @logger if @logger
|
|
267
|
+
RedisCloudAuth.register!
|
|
268
|
+
self.class.credentials_provider_registered!
|
|
269
|
+
end
|
|
270
|
+
|
|
213
271
|
def redis_sentinel_options
|
|
214
272
|
sentinels = @opts[:redis_sentinels]
|
|
215
273
|
|
|
@@ -259,10 +317,11 @@ module GitLab
|
|
|
259
317
|
Sidekiq.redis do |conn|
|
|
260
318
|
@connected = (conn.ping == "PONG")
|
|
261
319
|
end
|
|
262
|
-
rescue
|
|
320
|
+
rescue RedisClient::Error => e
|
|
321
|
+
# Sidekiq 7 talks to Redis through redis-client, which raises
|
|
322
|
+
# RedisClient::Error (not Redis::BaseConnectionError) on connection
|
|
323
|
+
# failures. redis-client reconnects on the next command, so just log.
|
|
263
324
|
@logger&.error "Error connecting to the Redis: #{e}"
|
|
264
|
-
pool = self.class.connection_pool[redis_options]
|
|
265
|
-
pool.reload(&:close)
|
|
266
325
|
@connected = false
|
|
267
326
|
end
|
|
268
327
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "gitlab_exporter/sidekiq"
|
|
3
|
+
require "redis_cloud_auth"
|
|
4
|
+
|
|
5
|
+
module GitLab
|
|
6
|
+
module Exporter
|
|
7
|
+
describe SidekiqProber, :integration do
|
|
8
|
+
describe "with a credentials_provider against an auth-protected Redis" do
|
|
9
|
+
let(:password) { ENV.fetch("REDIS_AUTH_PASSWORD", "test-token-secret") }
|
|
10
|
+
# The URL carries no password; the credentials_provider supplies it via a
|
|
11
|
+
# callable, exercising the full rotating-token path end to end.
|
|
12
|
+
let(:redis_url) { ENV.fetch("REDIS_AUTH_URL", "redis://localhost:6380") }
|
|
13
|
+
let(:metrics) { PrometheusMetrics.new }
|
|
14
|
+
|
|
15
|
+
# Stands in for a cloud-identity provider: returns a fixed token so the
|
|
16
|
+
# callable password + re-auth middleware run against a real Redis that
|
|
17
|
+
# requires AUTH.
|
|
18
|
+
let(:provider) do
|
|
19
|
+
pw = password
|
|
20
|
+
Class.new do
|
|
21
|
+
define_method(:call) do [nil, pw] end
|
|
22
|
+
define_method(:expires_at) { Time.now + 3600 }
|
|
23
|
+
end.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
# Each prober builds its own pool, so clear any pool cached by an
|
|
28
|
+
# earlier example to force a fresh connection for this target.
|
|
29
|
+
SidekiqProber.connection_pool.clear
|
|
30
|
+
RedisCloudAuth.register!
|
|
31
|
+
RedisCloudAuth::Registry.store({ provider: :static_test }, provider)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
after do RedisCloudAuth::Registry.clear! end
|
|
35
|
+
|
|
36
|
+
it "reaches the auth-protected Redis only when the callable password is supplied" do
|
|
37
|
+
prober = described_class.new(
|
|
38
|
+
metrics: metrics,
|
|
39
|
+
redis_url: redis_url,
|
|
40
|
+
redis_credentials_provider: { provider: :static_test }
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
prober.probe_stats
|
|
44
|
+
prober.probe_future_sets
|
|
45
|
+
|
|
46
|
+
# A populated metric proves the probe authenticated and ran against
|
|
47
|
+
# the target Redis; without the token it never gets this far.
|
|
48
|
+
expect(metrics.to_s).to include("sidekiq_jobs_processed_total")
|
|
49
|
+
expect(metrics.to_s).to include("sidekiq_schedule_set_backlog_count")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "does not connect to the auth-protected Redis without a token" do
|
|
53
|
+
SidekiqProber.connection_pool.clear
|
|
54
|
+
|
|
55
|
+
prober = described_class.new(metrics: metrics, redis_url: redis_url)
|
|
56
|
+
|
|
57
|
+
# Guards against a hollow test: the target really rejects unauthenticated
|
|
58
|
+
# clients, so the probe above only passes because of the callable password.
|
|
59
|
+
prober.probe_stats
|
|
60
|
+
|
|
61
|
+
expect(metrics.to_s).not_to include("sidekiq_jobs_processed_total")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "gitlab_exporter/sidekiq"
|
|
3
|
+
require "redis_client"
|
|
4
|
+
|
|
5
|
+
module GitLab
|
|
6
|
+
module Exporter
|
|
7
|
+
# Exercises probe_future_sets end to end. It issues a raw ZRANGEBYSCORE
|
|
8
|
+
# (redis-client cannot serialize the limit:/withscores: keyword form), so
|
|
9
|
+
# this guards that the command and its RESP3 [[member, score]] parsing keep
|
|
10
|
+
# working on the Sidekiq 7 / redis-client stack.
|
|
11
|
+
describe SidekiqProber, :integration do
|
|
12
|
+
let(:redis_url) { ENV.fetch("REDIS_URL", "redis://localhost:6379") }
|
|
13
|
+
let(:metrics) { PrometheusMetrics.new }
|
|
14
|
+
let(:seed) { RedisClient.new(url: redis_url) }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
seed.call("DEL", "schedule", "retry")
|
|
18
|
+
# A job due two minutes ago, so the set has a positive processing delay.
|
|
19
|
+
seed.call("ZADD", "schedule", (Time.now - 120).to_f.to_s, '{"class":"SomeWorker"}')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
after do seed.call("DEL", "schedule", "retry") end
|
|
23
|
+
|
|
24
|
+
it "emits backlog and delay metrics from the raw ZRANGEBYSCORE path" do
|
|
25
|
+
described_class.new(metrics: metrics, redis_url: redis_url).probe_future_sets
|
|
26
|
+
|
|
27
|
+
output = metrics.to_s
|
|
28
|
+
expect(output).to match(/^sidekiq_schedule_set_backlog_count 1\b/)
|
|
29
|
+
# A due job yields a non-zero delay, proving [member, score] was parsed.
|
|
30
|
+
delay = output[/^sidekiq_schedule_set_processing_delay_seconds (\S+)/, 1].to_f
|
|
31
|
+
expect(delay).to be > 0
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/spec/sidekiq_spec.rb
CHANGED
|
@@ -90,5 +90,72 @@ describe GitLab::Exporter::SidekiqProber do
|
|
|
90
90
|
expect(options[:username]).to eq("user")
|
|
91
91
|
expect(options[:password]).to eq("pass")
|
|
92
92
|
end
|
|
93
|
+
|
|
94
|
+
it "uses a rotating-token callable password and middleware descriptor with a credentials_provider" do
|
|
95
|
+
prober = described_class.new(
|
|
96
|
+
metrics: metrics,
|
|
97
|
+
redis_url: "rediss://cache.example.com:10000",
|
|
98
|
+
redis_username: "identity-object-id",
|
|
99
|
+
redis_credentials_provider: { provider: "azure", resource: "https://redis.azure.com" }
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
options = prober.send(:redis_options)
|
|
103
|
+
|
|
104
|
+
expect(options[:username]).to eq("identity-object-id")
|
|
105
|
+
expect(options[:password]).to be_a(RedisCloudAuth::TokenCredential)
|
|
106
|
+
expect(options[:custom][:credentials_provider]).to eq(
|
|
107
|
+
provider: "azure", resource: "https://redis.azure.com", username: "identity-object-id"
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "omits username from the descriptor when no redis_username is set" do
|
|
112
|
+
prober = described_class.new(
|
|
113
|
+
metrics: metrics,
|
|
114
|
+
redis_url: "rediss://cache.example.com:10000",
|
|
115
|
+
redis_credentials_provider: { provider: "azure", resource: "https://redis.azure.com" }
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
options = prober.send(:redis_options)
|
|
119
|
+
|
|
120
|
+
# A nil username would not match the provider registered under
|
|
121
|
+
# { provider: "azure", resource: ... } by exact Hash equality.
|
|
122
|
+
expect(options[:custom][:credentials_provider]).not_to have_key(:username)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "sets sentinels (and carries the master name in the URL) when configured" do
|
|
126
|
+
prober = described_class.new(
|
|
127
|
+
metrics: metrics,
|
|
128
|
+
redis_url: "redis://mymaster",
|
|
129
|
+
redis_sentinels: [{ host: "localhost", port: 26_379 }]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
options = prober.send(:redis_options)
|
|
133
|
+
|
|
134
|
+
# redis-client derives the Sentinel master name from the URL host, which
|
|
135
|
+
# is how Omnibus and CNG pass it, so no separate :name key is needed.
|
|
136
|
+
expect(options[:url]).to eq("redis://mymaster")
|
|
137
|
+
expect(options[:sentinels]).to eq([{ host: "localhost", port: 26_379 }])
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "omits sentinels when none are configured" do
|
|
141
|
+
prober = described_class.new(metrics: metrics, redis_url: "redis://localhost:6379")
|
|
142
|
+
|
|
143
|
+
options = prober.send(:redis_options)
|
|
144
|
+
|
|
145
|
+
expect(options).not_to have_key(:sentinels)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#connected?" do
|
|
150
|
+
let(:prober) { described_class.new(metrics: metrics, redis_url: "redis://localhost:6379") }
|
|
151
|
+
|
|
152
|
+
it "returns false without raising when Redis is unreachable" do
|
|
153
|
+
# Sidekiq 7 raises RedisClient::Error (not Redis::BaseConnectionError).
|
|
154
|
+
allow(Sidekiq).to receive(:redis).and_raise(RedisClient::ConnectionError.new("down"))
|
|
155
|
+
|
|
156
|
+
result = nil
|
|
157
|
+
expect { result = prober.send(:connected?) }.not_to raise_error
|
|
158
|
+
expect(result).to be(false)
|
|
159
|
+
end
|
|
93
160
|
end
|
|
94
161
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-exporter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 18.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pablo Carranza
|
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2016-07-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: az-identity
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: base64
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -98,44 +112,70 @@ dependencies:
|
|
|
98
112
|
name: redis
|
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
|
100
114
|
requirements:
|
|
101
|
-
- -
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '5'
|
|
118
|
+
- - "<"
|
|
102
119
|
- !ruby/object:Gem::Version
|
|
103
|
-
version:
|
|
120
|
+
version: '6'
|
|
104
121
|
type: :runtime
|
|
105
122
|
prerelease: false
|
|
106
123
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
124
|
requirements:
|
|
108
|
-
- -
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: '5'
|
|
128
|
+
- - "<"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '6'
|
|
131
|
+
- !ruby/object:Gem::Dependency
|
|
132
|
+
name: redis-cloud-auth
|
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.1'
|
|
138
|
+
type: :runtime
|
|
139
|
+
prerelease: false
|
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
109
143
|
- !ruby/object:Gem::Version
|
|
110
|
-
version:
|
|
144
|
+
version: '0.1'
|
|
111
145
|
- !ruby/object:Gem::Dependency
|
|
112
146
|
name: redis-namespace
|
|
113
147
|
requirement: !ruby/object:Gem::Requirement
|
|
114
148
|
requirements:
|
|
115
|
-
- -
|
|
149
|
+
- - "~>"
|
|
116
150
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 1.11
|
|
151
|
+
version: '1.11'
|
|
118
152
|
type: :runtime
|
|
119
153
|
prerelease: false
|
|
120
154
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
155
|
requirements:
|
|
122
|
-
- -
|
|
156
|
+
- - "~>"
|
|
123
157
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: 1.11
|
|
158
|
+
version: '1.11'
|
|
125
159
|
- !ruby/object:Gem::Dependency
|
|
126
160
|
name: sidekiq
|
|
127
161
|
requirement: !ruby/object:Gem::Requirement
|
|
128
162
|
requirements:
|
|
129
|
-
- -
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '7'
|
|
166
|
+
- - "<"
|
|
130
167
|
- !ruby/object:Gem::Version
|
|
131
|
-
version:
|
|
168
|
+
version: '8'
|
|
132
169
|
type: :runtime
|
|
133
170
|
prerelease: false
|
|
134
171
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
172
|
requirements:
|
|
136
|
-
- -
|
|
173
|
+
- - ">="
|
|
174
|
+
- !ruby/object:Gem::Version
|
|
175
|
+
version: '7'
|
|
176
|
+
- - "<"
|
|
137
177
|
- !ruby/object:Gem::Version
|
|
138
|
-
version:
|
|
178
|
+
version: '8'
|
|
139
179
|
- !ruby/object:Gem::Dependency
|
|
140
180
|
name: webrick
|
|
141
181
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -243,6 +283,8 @@ files:
|
|
|
243
283
|
- spec/git_process_proper_spec.rb
|
|
244
284
|
- spec/git_spec.rb
|
|
245
285
|
- spec/integration/cli_spec.rb
|
|
286
|
+
- spec/integration/sidekiq_credentials_provider_spec.rb
|
|
287
|
+
- spec/integration/sidekiq_future_sets_spec.rb
|
|
246
288
|
- spec/memstats_spec.rb
|
|
247
289
|
- spec/prometheus_metrics_spec.rb
|
|
248
290
|
- spec/ruby_spec.rb
|
|
@@ -289,6 +331,8 @@ test_files:
|
|
|
289
331
|
- spec/git_process_proper_spec.rb
|
|
290
332
|
- spec/git_spec.rb
|
|
291
333
|
- spec/integration/cli_spec.rb
|
|
334
|
+
- spec/integration/sidekiq_credentials_provider_spec.rb
|
|
335
|
+
- spec/integration/sidekiq_future_sets_spec.rb
|
|
292
336
|
- spec/memstats_spec.rb
|
|
293
337
|
- spec/prometheus_metrics_spec.rb
|
|
294
338
|
- spec/ruby_spec.rb
|