gitlab-labkit 5.1.1 → 5.1.2
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-asdf-versions.yml +2 -2
- data/.gitlab-ci.yml +16 -43
- data/.pre-commit-config.yaml +1 -1
- data/.tool-versions +2 -2
- data/README.md +12 -10
- data/lib/labkit/rate_limit/README.md +5 -2
- data/lib/labkit/rate_limit/evaluator.rb +13 -2
- data/lib/labkit/rate_limit/limiter.rb +2 -0
- data/lib/labkit/rate_limit/metrics.rb +0 -10
- data/lib/labkit/rate_limit/result.rb +4 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d272acd766a807cc483a70b13238a55b91dbffd0de51435c3e754df64562176d
|
|
4
|
+
data.tar.gz: 2fbc2ff23bbedca43ef1e5c650b3b6c78a0aa4c7ecfec85fec8eae79ef2d2752
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b469df64589bfe387e3d725d1cc6c6a3e769bc078ccb043b8afdf5f3591bae10a5730ecaa87069b1dc49041f50e6c6e92e145989a2a644e2d2f33fe56abd614f
|
|
7
|
+
data.tar.gz: 212bf5a04d9a38b4cd64f6b7319179dba2379f3b89b6b1c6070bf64f9f256ea899f6a02ddc35b271dcb6bfb300100627decd388b7b5e3ae5c45f289d2fdd4bfe
|
data/.gitlab-ci.yml
CHANGED
|
@@ -19,13 +19,13 @@ include:
|
|
|
19
19
|
# It includes standard checks, gitlab-scanners, validations and release processes
|
|
20
20
|
# common to all projects using this template library.
|
|
21
21
|
# see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/templates/standard.md
|
|
22
|
-
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/standard-build@v5.
|
|
22
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/standard-build@v5.24
|
|
23
23
|
|
|
24
24
|
# Runs rspec tests and rubocop on the project
|
|
25
25
|
# see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/templates/ruby.md
|
|
26
|
-
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/ruby-build@v5.
|
|
26
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/ruby-build@v5.24
|
|
27
27
|
|
|
28
|
-
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/danger@v5.
|
|
28
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/danger@v5.24
|
|
29
29
|
|
|
30
30
|
# Attach a redis service to the rspec job from common-ci-tasks/ruby-build.
|
|
31
31
|
# GitLab merges keys when a local job has the same name as an included one,
|
|
@@ -67,48 +67,21 @@ deploy:
|
|
|
67
67
|
- Gemfile
|
|
68
68
|
- .gitlab-ci.yml
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
70
|
+
# Gate SDK changes on the labkit-spec conformance suite, run remotely: trigger
|
|
71
|
+
# a downstream pipeline in labkit-spec that checks out this pipeline's commit
|
|
72
|
+
# (LABKIT_RUBY_SHA) and runs the Ruby conformance suite against it, verifying
|
|
73
|
+
# structured-logging, lifecycle, and rate-limit parity with the spec.
|
|
74
|
+
# strategy:depend mirrors the downstream result here, so a red run blocks the
|
|
75
|
+
# merge. See https://gitlab.com/gitlab-org/theseus/theseus/-/work_items/30
|
|
76
|
+
conformance-spec:
|
|
74
77
|
stage: validate
|
|
75
|
-
image:
|
|
76
|
-
name: jdxcode/mise:2026.9.7
|
|
77
|
-
entrypoint: [""]
|
|
78
78
|
needs: []
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
alias: redis
|
|
79
|
+
variables:
|
|
80
|
+
LABKIT_RUBY_SHA: $CI_COMMIT_SHA
|
|
82
81
|
rules:
|
|
83
|
-
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
84
|
-
- if: $CI_COMMIT_BRANCH
|
|
85
|
-
changes: !reference [.conformance-trigger-paths, changes]
|
|
86
82
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
87
83
|
changes: !reference [.conformance-trigger-paths, changes]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
MISE_TRUSTED_CONFIG_PATHS: $LABKIT_SPEC_DIR
|
|
93
|
-
MISE_DATA_DIR: $CI_PROJECT_DIR/.mise-cache
|
|
94
|
-
BUNDLE_PATH: vendor/bundle
|
|
95
|
-
CONFORMANCE_REDIS_ADDR: redis:6379
|
|
96
|
-
cache:
|
|
97
|
-
key: conformance-servers
|
|
98
|
-
paths:
|
|
99
|
-
- .labkit-spec/conformance/servers/ruby/vendor/
|
|
100
|
-
- .mise-cache/
|
|
101
|
-
script:
|
|
102
|
-
# Not clone: the restored cache makes LABKIT_SPEC_DIR non-empty.
|
|
103
|
-
- git init -q "$LABKIT_SPEC_DIR"
|
|
104
|
-
- git -C "$LABKIT_SPEC_DIR" fetch -q --depth 1 "$LABKIT_SPEC_REPO" "$LABKIT_SPEC_REF"
|
|
105
|
-
- git -C "$LABKIT_SPEC_DIR" checkout -q FETCH_HEAD
|
|
106
|
-
- cd "$LABKIT_SPEC_DIR"
|
|
107
|
-
- mise install
|
|
108
|
-
# The gemspec lists files via `git ls-files`, so the path must be a git work tree.
|
|
109
|
-
# grep fails the job if the Gemfile line moved and sed matched nothing.
|
|
110
|
-
- |
|
|
111
|
-
sed -i 's|^gem "gitlab-labkit".*|gem "gitlab-labkit", path: "'"$CI_PROJECT_DIR"'"|' conformance/servers/ruby/Gemfile
|
|
112
|
-
grep -q 'gem "gitlab-labkit", path: "'"$CI_PROJECT_DIR"'"' conformance/servers/ruby/Gemfile
|
|
113
|
-
- mise run conformance:ruby
|
|
114
|
-
timeout: 15m
|
|
84
|
+
trigger:
|
|
85
|
+
project: gitlab-org/quality/tooling/labkit-spec
|
|
86
|
+
branch: main
|
|
87
|
+
strategy: depend
|
data/.pre-commit-config.yaml
CHANGED
|
@@ -25,7 +25,7 @@ repos:
|
|
|
25
25
|
# Documentation available at
|
|
26
26
|
# https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/docs/pre-commit.md
|
|
27
27
|
- repo: https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks
|
|
28
|
-
rev: v5.
|
|
28
|
+
rev: v5.24 # renovate:managed
|
|
29
29
|
|
|
30
30
|
hooks:
|
|
31
31
|
- id: shellcheck # Run shellcheck for changed Shell files
|
data/.tool-versions
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
ruby 4.0.
|
|
2
|
-
shfmt 3.
|
|
1
|
+
ruby 4.0.7
|
|
2
|
+
shfmt 3.14
|
|
3
3
|
shellcheck 0.11
|
data/README.md
CHANGED
|
@@ -55,13 +55,16 @@ To opt out of autostart (e.g. you've started Redis some other way), set
|
|
|
55
55
|
`LABKIT_TEST_REDIS_URL` to a reachable instance, or
|
|
56
56
|
`LABKIT_TEST_REDIS_NO_AUTOSTART=1` to fail loudly instead of spawning.
|
|
57
57
|
|
|
58
|
-
The `conformance` CI job
|
|
58
|
+
The `conformance-spec` CI job gates merge requests on the
|
|
59
59
|
[labkit-spec](https://gitlab.com/gitlab-org/quality/tooling/labkit-spec)
|
|
60
|
-
conformance harness
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
conformance harness. It triggers a downstream pipeline in labkit-spec that
|
|
61
|
+
checks out the MR's commit and runs the Ruby conformance suite against it,
|
|
62
|
+
with the `gitlab-labkit` gem installed from that checkout instead of the
|
|
63
|
+
released version. A change to logging, lifecycle, or rate limiting is
|
|
64
|
+
therefore checked against the cross-SDK scenarios before it merges, and the
|
|
65
|
+
downstream result is mirrored here, so a red run blocks the merge. To run it
|
|
66
|
+
locally, clone labkit-spec and replace the `gitlab-labkit` line in
|
|
67
|
+
`conformance/servers/ruby/Gemfile` with
|
|
65
68
|
|
|
66
69
|
```ruby
|
|
67
70
|
gem "gitlab-labkit", path: "/path/to/labkit-ruby"
|
|
@@ -71,10 +74,9 @@ then run `mise run conformance:ruby` from the spec checkout (the rate-limit
|
|
|
71
74
|
aspect needs Docker or `CONFORMANCE_REDIS_ADDR`).
|
|
72
75
|
|
|
73
76
|
If your change also needs a labkit-spec change (a new scenario, or an update
|
|
74
|
-
to its Ruby server), open that MR first
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`master`.
|
|
77
|
+
to its Ruby server), open and merge that MR first. The trigger always runs
|
|
78
|
+
labkit-spec `main`, so the spec side has to land before the gate here can
|
|
79
|
+
pass.
|
|
78
80
|
|
|
79
81
|
Please also review the [development section of the LabKit (go) README](https://gitlab.com/gitlab-org/labkit#developing-labkit) for details of the LabKit architectural philosophy.
|
|
80
82
|
|
|
@@ -469,7 +469,7 @@ result.to_response_headers
|
|
|
469
469
|
| `resolved_limit` | The evaluated `Integer` limit for this check. |
|
|
470
470
|
| `resolved_period` | The evaluated `Integer` period in seconds for this check. |
|
|
471
471
|
| `count` | Raw INCR value; useful for utilization-ratio metrics. |
|
|
472
|
-
| `remaining` | `
|
|
472
|
+
| `remaining` | `resolved_limit - count`, clamped to `[0, max(resolved_limit, 0)]`. |
|
|
473
473
|
| `reset_at` | Best-effort UTC `Time` when the window resets (advisory only). |
|
|
474
474
|
|
|
475
475
|
`to_response_headers` returns `{}` for an unmatched or error result, so it is
|
|
@@ -483,6 +483,10 @@ logged at WARN with `error_type: "rate_limit_error"` and returned as an error
|
|
|
483
483
|
`Result` (`matched?` false, `error?` true, `action` `:allow`). The caller
|
|
484
484
|
should treat the request as allowed.
|
|
485
485
|
|
|
486
|
+
A negative `cost:` takes the same path: it would credit quota back, so `check`
|
|
487
|
+
refuses it before touching any rule. Nothing is counted and the WARN line
|
|
488
|
+
carries `rule: null`.
|
|
489
|
+
|
|
486
490
|
A failed-open `check` is still counted: it emits
|
|
487
491
|
`checks_total{action="allow", matched="false", error="true"}`, so the fraction
|
|
488
492
|
of checks that encountered an error is `checks_total{error="true"}` over
|
|
@@ -514,7 +518,6 @@ flooding).
|
|
|
514
518
|
| `gitlab_labkit_rate_limiter_checks_total` | counter | `rate_limiter`, `action`, `matched`, `error` | Exactly one increment per `check` call, including fail-open. `action` is what the caller should do (`"allow"` or `"block"`); `matched` and `error` are `"true"`/`"false"`. |
|
|
515
519
|
| `gitlab_labkit_rate_limiter_rule_evaluations_total` | counter | `rate_limiter`, `rule`, `action`, `result` | One increment per evaluated rule (plus one per matched `:skip` rule). `action` is the configured rule action (`"limit"`, `"log"`, `"skip"`); `result` is what the evaluation decided (`"allow"`, `"block"`, `"log"`, `"skip"`, `"banned"` — see the Actions table). |
|
|
516
520
|
| `gitlab_labkit_rate_limiter_peeks_total` | counter | `rate_limiter`, `error` | Exactly one increment per `peek` call, including fail-open. `error` is `"true"`/`"false"`. |
|
|
517
|
-
| `gitlab_labkit_rate_limiter_enforced_total` | counter | `rate_limiter`, `rule` | One increment per request the caller rejected on a `:block`. Emitted by the caller, not the evaluator: a rollout gate may observe a block without enforcing it. |
|
|
518
521
|
| `gitlab_labkit_rate_limiter_limit` | gauge | `rate_limiter`, `rule` | Resolved limit at the last check (useful when `limit:` is callable). |
|
|
519
522
|
| `gitlab_labkit_rate_limiter_period_seconds` | gauge | `rate_limiter`, `rule` | Resolved period at the last check. |
|
|
520
523
|
|
|
@@ -120,6 +120,9 @@ module Labkit
|
|
|
120
120
|
|
|
121
121
|
def check(identifier, cost: 1, rule_context: nil)
|
|
122
122
|
cursor = RuleCursor.new
|
|
123
|
+
# A negative cost would credit quota back and open the window for good.
|
|
124
|
+
raise ArgumentError, "cost is #{cost}, must not be negative" if cost.negative?
|
|
125
|
+
|
|
123
126
|
result = check_rules(identifier, cost, rule_context, cursor)
|
|
124
127
|
rescue StandardError => e
|
|
125
128
|
# Intentionally broad: fail-open applies to any unexpected error (network,
|
|
@@ -372,7 +375,7 @@ module Labkit
|
|
|
372
375
|
count: count,
|
|
373
376
|
# A ban outlives its counter, so count reads 0 once the window has
|
|
374
377
|
# gone. Nothing is remaining while the ban still blocks.
|
|
375
|
-
remaining: banned ? 0 :
|
|
378
|
+
remaining: banned ? 0 : remaining_of(resolved_limit, count),
|
|
376
379
|
reset_at: Time.now.utc + (banned ? ban_ttl : window_remaining)
|
|
377
380
|
)
|
|
378
381
|
|
|
@@ -383,13 +386,21 @@ module Labkit
|
|
|
383
386
|
info = Result::Info.new(
|
|
384
387
|
resolved_limit: resolved_limit, resolved_period: resolved_period,
|
|
385
388
|
count: count,
|
|
386
|
-
remaining:
|
|
389
|
+
remaining: remaining_of(resolved_limit, count),
|
|
387
390
|
reset_at: Time.now.utc + (ttl >= 0 ? ttl : resolved_period)
|
|
388
391
|
)
|
|
389
392
|
|
|
390
393
|
Result::Evaluation.new(rule: rule, exceeded: count > resolved_limit, info: info)
|
|
391
394
|
end
|
|
392
395
|
|
|
396
|
+
# RateLimit-Remaining must not exceed RateLimit-Limit, and a counter can
|
|
397
|
+
# sit below zero if an older client credited it. A limit resolving to a
|
|
398
|
+
# non-positive value means "deny all", so the ceiling floors at zero too.
|
|
399
|
+
def remaining_of(resolved_limit, count)
|
|
400
|
+
ceiling = [resolved_limit, 0].max
|
|
401
|
+
(ceiling - count).clamp(0, ceiling)
|
|
402
|
+
end
|
|
403
|
+
|
|
393
404
|
# The braces are the cluster hash tag: a counter and its ban must share a
|
|
394
405
|
# slot because BAN_SCRIPT touches both in one call. The rule name stays
|
|
395
406
|
# inside it so one identifier's keys spread across nodes instead of piling
|
|
@@ -35,6 +35,8 @@ module Labkit
|
|
|
35
35
|
# @param cost [Numeric] amount to add to the counter. Defaults to 1
|
|
36
36
|
# (count-mode). Pass a non-1 Numeric for cost-mode counters such as
|
|
37
37
|
# resource-usage limits; passing 0 reads the counter without writing.
|
|
38
|
+
# A negative cost would credit quota back, so the check fails open and
|
|
39
|
+
# logs instead of counting.
|
|
38
40
|
# @param rule_context [Hash, nil] optional per-request context passed to
|
|
39
41
|
# one-arity callables on +limit+/+period+. Lets rules resolve dynamic
|
|
40
42
|
# configuration (e.g. per-namespace settings) without rebuilding the
|
|
@@ -71,16 +71,6 @@ module Labkit
|
|
|
71
71
|
)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Emitted by the caller, once per rejected request: only the caller knows
|
|
75
|
-
# whether it acted on a :block (a rollout gate may observe without enforcing).
|
|
76
|
-
def enforced_total
|
|
77
|
-
Labkit::Metrics::Client.counter(
|
|
78
|
-
:gitlab_labkit_rate_limiter_enforced_total,
|
|
79
|
-
'Total number of requests rejected on a rate limit block',
|
|
80
|
-
{ rate_limiter: nil, rule: nil }
|
|
81
|
-
)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
74
|
def limit_gauge
|
|
85
75
|
Labkit::Metrics::Client.gauge(
|
|
86
76
|
:gitlab_labkit_rate_limiter_limit,
|
|
@@ -169,8 +169,10 @@ module Labkit
|
|
|
169
169
|
# for default cost=1 callers, fractional for cost-mode callers.
|
|
170
170
|
# Pre-2.x releases exposed this as Integer; see the migration
|
|
171
171
|
# note in the cost-aware Lua script change.
|
|
172
|
-
# remaining - requests remaining before the limit is hit
|
|
173
|
-
#
|
|
172
|
+
# remaining - requests remaining before the limit is hit, clamped to
|
|
173
|
+
# [0, resolved_limit]; 0 throughout when the limit is
|
|
174
|
+
# non-positive. Inherits Float typing from count when
|
|
175
|
+
# count is fractional.
|
|
174
176
|
# reset_at - best-effort UTC Time when the counter window resets
|
|
175
177
|
Result::Info = Data.define(:resolved_limit, :resolved_period, :count, :remaining, :reset_at)
|
|
176
178
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-labkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.1.
|
|
4
|
+
version: 5.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Newdigate
|
|
@@ -722,7 +722,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
722
722
|
- !ruby/object:Gem::Version
|
|
723
723
|
version: '0'
|
|
724
724
|
requirements: []
|
|
725
|
-
rubygems_version: 4.0.
|
|
725
|
+
rubygems_version: 4.0.20
|
|
726
726
|
specification_version: 4
|
|
727
727
|
summary: Instrumentation for GitLab
|
|
728
728
|
test_files: []
|