gitlab-exporter 17.1.0 → 17.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df145039780458bab34b436ca149dce37c4c4f4a08e1707b606f70dc4aa021a4
4
- data.tar.gz: 272f4e78ffb5901954f086e58a158087328d0478deb18114fb748504e067796b
3
+ metadata.gz: 9f67afb938a5a207bd67f7402559c6bf2c62e8c48e2d24660c052361727e862a
4
+ data.tar.gz: ec77e34ad3640e64d368a00a844d91e651d64d1c300a8f8a82de3c4c0567fb8b
5
5
  SHA512:
6
- metadata.gz: a223ec3d899fcc85eeb391e100cce3942aeb050264cc03c6877ad6387f09234461408f45f1a8a82128c657b240f4ce2768c35a1d58857d4c3fb212f42090f28c
7
- data.tar.gz: 992f12485329239412a793b67c556c7226535765d3ecfea83271e055c679c75d7d1d0af8b0be71e83408a0aacc77ceb0e3d9079281725d26b04f86955e01baf7
6
+ metadata.gz: 17a4dd08119dc1f47eb6f6867b466e91090ff3b673ed3d703792e11764dff5a8686c84ad22cc19168bdd9ec66759260956286138bef79bbcece8fc3928c476c9
7
+ data.tar.gz: bad62ae9548056c970ce2b4c0fb84aa3d60879e397f17e7e97fa0c1a98116174ae7ea6b6d684972b01d1d4f4301fa1c892ec733850ef1fccde08fb0367d15f0f
data/CONTRIBUTING.md CHANGED
@@ -61,7 +61,7 @@ abbreviation.
61
61
  If you have read this guide and want to know how the GitLab [core team]
62
62
  operates please see [the GitLab contributing process](PROCESS.md).
63
63
 
64
- - [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
64
+ - [GitLab Inc engineers should refer to the engineering workflow document](https://handbook.gitlab.com/handbook/engineering/workflow/)
65
65
 
66
66
  ## Security vulnerability disclosure
67
67
 
@@ -266,7 +266,7 @@ on those issues. Please select someone with relevant experience from the
266
266
  [GitLab team][team]. If there is nobody mentioned with that expertise look in
267
267
  the commit history for the affected files to find someone.
268
268
 
269
- [described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
269
+ [described in our handbook]: https://handbook.gitlab.com/handbook/product-development/how-we-work/issue-triage/
270
270
  [issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
271
271
 
272
272
  ### Feature proposals
@@ -512,7 +512,7 @@ When having your code reviewed and when reviewing merge requests please take the
512
512
  making and testing future changes
513
513
  1. Changes do not adversely degrade performance.
514
514
  - Avoid repeated polling of endpoints that require a significant amount of overhead
515
- - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
515
+ - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/development/database/query_recorder/)
516
516
  - Avoid repeated access of filesystem
517
517
  1. If you need polling to support real-time features, please use
518
518
  [polling with ETag caching][polling-etag].
@@ -641,10 +641,9 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
641
641
  [js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
642
642
  [scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
643
643
  [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
644
- [UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
644
+ [UX Guide for GitLab]: https://design.gitlab.com/
645
645
  [license-finder-doc]: doc/development/licensing.md
646
- [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
647
- [polling-etag]: https://docs.gitlab.com/ce/development/polling.html
646
+ [polling-etag]: https://docs.gitlab.com/development/polling/
648
647
  [testing]: doc/development/testing.md
649
648
 
650
649
  [^1]: Please note that specs other than JavaScript specs are considered backend
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (17.1.0)
4
+ gitlab-exporter (17.2.0)
5
5
  base64 (= 0.3.0)
6
6
  connection_pool (= 2.5.5)
7
7
  deep_merge (~> 1.2.2)
8
- faraday (= 2.14.4)
8
+ faraday (= 2.15.1)
9
9
  pg (= 1.6.3)
10
10
  quantile (= 0.2.1)
11
11
  redis (= 4.8.1)
@@ -21,8 +21,8 @@ GEM
21
21
  connection_pool (2.5.5)
22
22
  deep_merge (1.2.2)
23
23
  diff-lcs (1.5.0)
24
- faraday (2.14.4)
25
- faraday-net_http (>= 2.0, < 3.5)
24
+ faraday (2.15.1)
25
+ faraday-net_http (>= 2.0, < 3.6)
26
26
  json
27
27
  logger
28
28
  faraday-net_http (3.0.2)
@@ -43,7 +43,7 @@ GEM
43
43
  redis (4.8.1)
44
44
  redis-namespace (1.11.0)
45
45
  redis (>= 4)
46
- regexp_parser (2.11.0)
46
+ regexp_parser (2.13.1)
47
47
  rspec (3.12.0)
48
48
  rspec-core (~> 3.12.0)
49
49
  rspec-expectations (~> 3.12.0)
@@ -57,14 +57,14 @@ GEM
57
57
  diff-lcs (>= 1.2.0, < 2.0)
58
58
  rspec-support (~> 3.12.0)
59
59
  rspec-support (3.12.0)
60
- rubocop (1.91.0)
60
+ rubocop (1.92.2)
61
61
  json (>= 2.3)
62
62
  language_server-protocol (~> 3.17.0.2)
63
63
  lint_roller (~> 1.1.0)
64
64
  parallel (>= 1.10)
65
65
  parser (>= 3.3.0.2)
66
66
  rainbow (>= 2.2.2, < 4.0)
67
- regexp_parser (>= 2.9.3, < 3.0)
67
+ regexp_parser (>= 2.11.3, < 3.0)
68
68
  rubocop-ast (>= 1.49.0, < 2.0)
69
69
  ruby-progressbar (~> 1.7)
70
70
  unicode-display_width (>= 2.4.0, < 4.0)
@@ -91,4 +91,4 @@ DEPENDENCIES
91
91
  rubocop (~> 1.0)
92
92
 
93
93
  BUNDLED WITH
94
- 4.0.18
94
+ 4.0.19
data/README.md CHANGED
@@ -38,7 +38,19 @@ metrics.
38
38
  Usage-data counters are excluded. Enable the `geo_node_status` probe in the `database`
39
39
  endpoint configuration.
40
40
  * [Zoekt](lib/gitlab_exporter/database/zoekt.rb) --
41
- `search_zoekt_task_processing_queue_size`
41
+ `search_zoekt_task_processing_queue_size`,
42
+ `search_zoekt_repositories_schema_version_count`,
43
+ `search_zoekt_nodes_status`,
44
+ `search_zoekt_node_unclaimed_storage_bytes`,
45
+ `search_zoekt_node_storage_percent_used`,
46
+ `search_zoekt_node_enabled_namespaces`,
47
+ `search_zoekt_node_tasks`,
48
+ `search_zoekt_repositories_states_total`,
49
+ `search_zoekt_indices_states`,
50
+ `search_zoekt_indices_watermark_levels`,
51
+ `search_zoekt_indices_reserved_storage_bytes`,
52
+ `search_zoekt_indices_used_storage_bytes`,
53
+ `search_zoekt_indices_with_stale_used_storage_bytes`
42
54
  1. Git
43
55
  * [git pull/push timings](lib/gitlab_exporter/git.rb) --
44
56
  `git_pull_time_milliseconds`, `git_push_time_milliseconds`
@@ -70,7 +82,7 @@ metrics.
70
82
  * `sidekiq_retry_set_processing_delay_seconds`
71
83
  * `sidekiq_retry_set_backlog_count`
72
84
  1. [Elasticsearch](lib/gitlab_exporter/elasticsearch.rb)
73
- * [Migrations](https://docs.gitlab.com/ee/integration/elasticsearch.html#advanced-search-migrations) -- `elasticsearch_migrations_state`
85
+ * [Migrations](https://docs.gitlab.com/integration/advanced_search/elasticsearch/#advanced-search-migrations) -- `elasticsearch_migrations_info`
74
86
 
75
87
  ### Setup with GitLab Development Kit
76
88
 
@@ -105,6 +117,34 @@ Once running, you can point your browser or curl to the following URLs:
105
117
  * http://localhost:9168/sidekiq
106
118
  * http://localhost:9168/metrics (to get all of the above combined)
107
119
 
120
+ #### Health checks
121
+
122
+ The web exporter also serves two static routes for health checks, such as
123
+ Kubernetes liveness and readiness probes:
124
+
125
+ * `/-/liveness`
126
+ * `/-/readiness`
127
+
128
+ Both return `200 OK` with a plain-text `OK` body. They run no probes and
129
+ make no connections to PostgreSQL, Redis, or Sidekiq, so a backend outage
130
+ shows up in the metrics instead of restarting the exporter. The server
131
+ accepts requests only after all probe routes are mounted, so readiness
132
+ currently behaves the same as liveness.
133
+
134
+ The routes are served by the same listener as the metrics, so set
135
+ `scheme: HTTPS` on the probes when `tls_enabled` is set. For example:
136
+
137
+ ```yaml
138
+ livenessProbe:
139
+ httpGet:
140
+ path: /-/liveness
141
+ port: 9168
142
+ readinessProbe:
143
+ httpGet:
144
+ path: /-/readiness
145
+ port: 9168
146
+ ```
147
+
108
148
  If it is undesirable to have secrets (e.g. Redis password or PostgreSQL credentials) in the config file,
109
149
  then you can specify an external command (with the `--extra-config-command <command>` flag) that outputs
110
150
  such credentials in a YAML form (same structure as the config file) and it will be merged with the file configuration.
@@ -77,9 +77,9 @@ probes:
77
77
  <<: *db_common_opts
78
78
  project_ids:
79
79
  - 1
80
- geo_node_status:
81
- class_name: Database::GeoNodeStatusProber
82
- <<: *db_common
80
+ geo_node_status:
81
+ class_name: Database::GeoNodeStatusProber
82
+ <<: *db_common
83
83
  zoekt:
84
84
  class_name: Database::ZoektProber
85
85
  <<: *db_common
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_runtime_dependency "base64", "0.3.0"
29
29
  s.add_runtime_dependency "connection_pool", "2.5.5"
30
30
  s.add_runtime_dependency "deep_merge", "~> 1.2.2"
31
- s.add_runtime_dependency "faraday", "2.14.4"
31
+ s.add_runtime_dependency "faraday", "2.15.1"
32
32
  s.add_runtime_dependency "pg", "1.6.3"
33
33
  s.add_runtime_dependency "quantile", "0.2.1"
34
34
  s.add_runtime_dependency "redis", "4.8.1"
@@ -176,8 +176,9 @@ module GitLab
176
176
  opts.on("-c config.yml", "Monitoring config") do |val|
177
177
  @config_file = val
178
178
  end
179
- opts.on("--extra-config-command command", "Shell command that returns YAML config to be merged
180
- with the config file") do |val|
179
+ opts.on("--extra-config-command command",
180
+ "Shell command that returns YAML config to be merged",
181
+ "with the config file") do |val|
181
182
  @extra_config_command = val
182
183
  end
183
184
  end
@@ -26,7 +26,7 @@ module GitLab
26
26
  end
27
27
 
28
28
  # Probes the state of Advanced Search Migrations
29
- # https://docs.gitlab.com/ee/integration/elasticsearch.html#advanced-search-migrations
29
+ # https://docs.gitlab.com/integration/advanced_search/elasticsearch/#advanced-search-migrations
30
30
  def probe_migrations
31
31
  elastic_probe do |conn|
32
32
  resp = conn.get "/gitlab-*-migrations/_search"
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "17.1.0".freeze
3
+ VERSION = "17.2.0".freeze
4
4
  end
5
5
  end
@@ -14,6 +14,11 @@ module GitLab
14
14
  DEFAULT_LISTEN_PORT = 9168
15
15
  DEFAULT_MEMORY_THRESHOLD = 1024
16
16
 
17
+ # Static routes for Kubernetes liveness and readiness probes. They run no
18
+ # probers, so a slow or failing backend never restarts the pod or removes it
19
+ # from Service endpoints. The `/-/` prefix keeps them apart from probe routes.
20
+ HEALTH_ROUTES = %w[/-/liveness /-/readiness].freeze
21
+
17
22
  # Security response headers previously added by Sinatra's Rack::Protection
18
23
  # middleware, which is gone now that we serve directly via WEBrick. Kept for
19
24
  # parity so scanners and consumers see the same headers as before.
@@ -34,6 +39,7 @@ module GitLab
34
39
 
35
40
  @server = build_server(server_config)
36
41
  mount_probes(config[:probes])
42
+ mount_health_routes
37
43
 
38
44
  # Defrag heap after everything is loaded into memory.
39
45
  GC.compact
@@ -90,6 +96,29 @@ module GitLab
90
96
  end
91
97
  end
92
98
 
99
+ # Mounted after the probes so these routes win over a probe that happens to
100
+ # share a path. The server only accepts requests once `run!` starts it,
101
+ # after `setup` has returned, so readiness currently matches liveness.
102
+ def mount_health_routes
103
+ HEALTH_ROUTES.each do |path|
104
+ @server.mount_proc(path) do |_request, response|
105
+ handle_health(response)
106
+ end
107
+ end
108
+ end
109
+
110
+ def handle_health(response)
111
+ write_response(response, "text/plain; charset=utf-8", "OK")
112
+ end
113
+
114
+ def write_response(response, content_type, body)
115
+ SECURITY_HEADERS.each do |header, value|
116
+ response[header] = value
117
+ end
118
+ response.content_type = content_type
119
+ response.body = body
120
+ end
121
+
93
122
  def handle_probe(response, opts)
94
123
  # A memory killer that kills the process if we exceeded a certain threshold.
95
124
  check_memory_threshold!
@@ -100,11 +129,7 @@ module GitLab
100
129
  buffer = StringIO.new
101
130
  prober.write_to(buffer)
102
131
 
103
- SECURITY_HEADERS.each do |header, value|
104
- response[header] = value
105
- end
106
- response.content_type = "text/plain; version=0.0.4; charset=utf-8"
107
- response.body = buffer.string
132
+ write_response(response, "text/plain; version=0.0.4; charset=utf-8", buffer.string)
108
133
  ensure
109
134
  # Performs a major GC after each request. We found that this helps to free up
110
135
  # several MB of memory in conjunction with stricter malloc config.
data/spec/cli_spec.rb CHANGED
@@ -32,6 +32,18 @@ context "With valid pair of repositories" do
32
32
  end
33
33
 
34
34
  describe GitLab::Exporter::CLI::Server do
35
+ describe "#help" do
36
+ it "aligns the --extra-config-command description with the other options" do
37
+ help = described_class.new([].extend(OptionParser::Arguable)).help
38
+
39
+ expect(help).to include(
40
+ " --extra-config-command command\n" \
41
+ " Shell command that returns YAML config to be merged\n" \
42
+ " with the config file\n"
43
+ )
44
+ end
45
+ end
46
+
35
47
  context "extra-config-command is passed" do
36
48
  let(:argv) do
37
49
  ["-c", "spec/fixtures/config.yml", "--extra-config-command", extra_config_command]
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+ require "yaml"
3
+
4
+ describe "config/gitlab-exporter.yml.example" do
5
+ let(:config_path) { File.expand_path("../config/gitlab-exporter.yml.example", __dir__) }
6
+ let(:config) { YAML.safe_load_file(config_path, aliases: true) }
7
+
8
+ it "parses as valid YAML with aliases" do
9
+ expect { config }.not_to raise_error
10
+ expect(config).to be_a(Hash)
11
+ end
12
+
13
+ describe "database probes" do
14
+ let(:database_probes) { config.dig("probes", "database") }
15
+
16
+ it "configures geo_node_status directly under probes.database" do
17
+ expect(database_probes).to include("geo_node_status")
18
+ expect(database_probes["geo_node_status"]).to include(
19
+ "class_name" => "Database::GeoNodeStatusProber",
20
+ "methods" => ["probe_db"]
21
+ )
22
+ end
23
+
24
+ it "keeps remote_mirrors and geo_node_status as sibling probes without nested leakage" do
25
+ expect(database_probes["remote_mirrors"]).not_to have_key("geo_node_status")
26
+ expect(database_probes.keys).to include(
27
+ "ci_builds",
28
+ "tuple_stats",
29
+ "rows_count",
30
+ "remote_mirrors",
31
+ "geo_node_status",
32
+ "zoekt"
33
+ )
34
+ end
35
+ end
36
+ end
@@ -2,6 +2,8 @@ require "spec_helper"
2
2
  require "gitlab_exporter/web_exporter"
3
3
  require "net/http"
4
4
  require "timeout"
5
+ require "openssl"
6
+ require "tmpdir"
5
7
 
6
8
  describe GitLab::Exporter::WebExporter do
7
9
  # Each test builds a fresh server via `.setup`; drop the reference afterwards
@@ -29,9 +31,22 @@ describe GitLab::Exporter::WebExporter do
29
31
  server = instance_double(WEBrick::HTTPServer)
30
32
  allow(WEBrick::HTTPServer).to receive(:new).and_return(server)
31
33
 
32
- expect(server).to receive(:mount_proc).with("/ruby")
34
+ allow(server).to receive(:mount_proc)
33
35
 
34
36
  described_class.setup(server: {}, probes: { ruby: { methods: %w[probe_gc], opts: {} } })
37
+
38
+ expect(server).to have_received(:mount_proc).with("/ruby")
39
+ end
40
+
41
+ it "mounts the health routes after the probes" do
42
+ server = instance_double(WEBrick::HTTPServer)
43
+ allow(WEBrick::HTTPServer).to receive(:new).and_return(server)
44
+ mounted = []
45
+ allow(server).to receive(:mount_proc) { |path| mounted << path }
46
+
47
+ described_class.setup(server: {}, probes: { ruby: { methods: %w[probe_gc], opts: {} } })
48
+
49
+ expect(mounted).to eq(%w[/ruby /-/liveness /-/readiness])
35
50
  end
36
51
 
37
52
  it "raises when TLS is enabled but no certificate is configured" do
@@ -45,7 +60,7 @@ describe GitLab::Exporter::WebExporter do
45
60
  captured = nil
46
61
  allow(WEBrick::HTTPServer).to receive(:new) do |**options|
47
62
  captured = options
48
- instance_double(WEBrick::HTTPServer)
63
+ instance_double(WEBrick::HTTPServer, mount_proc: nil)
49
64
  end
50
65
 
51
66
  described_class.setup(config.merge(probes: {}))
@@ -123,5 +138,81 @@ describe GitLab::Exporter::WebExporter do
123
138
 
124
139
  expect(response.code).to eq("404")
125
140
  end
141
+
142
+ %w[/-/liveness /-/readiness].each do |path|
143
+ describe path do
144
+ it "returns a static 200 without running any probers" do
145
+ expect(GitLab::Exporter::Prober).not_to receive(:new)
146
+
147
+ response = Net::HTTP.get_response(URI("#{base_uri}#{path}"))
148
+
149
+ expect(response.code).to eq("200")
150
+ expect(response.content_type).to eq("text/plain")
151
+ expect(response.body).to eq("OK")
152
+ expect(response["X-Content-Type-Options"]).to eq("nosniff")
153
+ end
154
+
155
+ it "does not check the memory threshold" do
156
+ expect(described_class).not_to receive(:check_memory_threshold!)
157
+
158
+ Net::HTTP.get_response(URI("#{base_uri}#{path}"))
159
+ end
160
+
161
+ it "answers HEAD requests without a body" do
162
+ uri = URI("#{base_uri}#{path}")
163
+ response = Net::HTTP.start(uri.host, uri.port) { |http| http.head(uri.path) }
164
+
165
+ expect(response.code).to eq("200")
166
+ expect(response.body).to be_nil
167
+ end
168
+ end
169
+ end
170
+
171
+ context "with TLS enabled" do
172
+ let(:tls_dir) { Dir.mktmpdir }
173
+ let(:config) do
174
+ key = OpenSSL::PKey::RSA.new(2048)
175
+ cert = self_signed_cert(key)
176
+ File.write(File.join(tls_dir, "tls.key"), key.to_pem)
177
+ File.write(File.join(tls_dir, "tls.crt"), cert.to_pem)
178
+
179
+ {
180
+ server: {
181
+ listen_address: "127.0.0.1",
182
+ listen_port: 0,
183
+ tls_enabled: true,
184
+ tls_cert_path: File.join(tls_dir, "tls.crt"),
185
+ tls_key_path: File.join(tls_dir, "tls.key")
186
+ },
187
+ probes: {}
188
+ }
189
+ end
190
+
191
+ after do
192
+ FileUtils.remove_entry(tls_dir)
193
+ end
194
+
195
+ it "serves the health routes over HTTPS" do
196
+ port = server.listeners.first.addr[1]
197
+ response = Net::HTTP.start("127.0.0.1", port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) { |http|
198
+ http.get("/-/liveness")
199
+ }
200
+
201
+ expect(response.code).to eq("200")
202
+ expect(response.body).to eq("OK")
203
+ end
204
+
205
+ def self_signed_cert(key)
206
+ cert = OpenSSL::X509::Certificate.new
207
+ cert.version = 2
208
+ cert.serial = 1
209
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/CN=127.0.0.1")
210
+ cert.public_key = key.public_key
211
+ cert.not_before = Time.now
212
+ cert.not_after = Time.now + 3600
213
+ cert.sign(key, OpenSSL::Digest.new("SHA256"))
214
+ cert
215
+ end
216
+ end
126
217
  end
127
218
  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: 17.1.0
4
+ version: 17.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 2.14.4
61
+ version: 2.15.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 2.14.4
68
+ version: 2.15.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pg
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -229,6 +229,7 @@ files:
229
229
  - lib/gitlab_exporter/version.rb
230
230
  - lib/gitlab_exporter/web_exporter.rb
231
231
  - spec/cli_spec.rb
232
+ - spec/config_spec.rb
232
233
  - spec/database/bloat_spec.rb
233
234
  - spec/database/ci_builds_spec.rb
234
235
  - spec/database/geo_node_status_spec.rb
@@ -274,6 +275,7 @@ specification_version: 4
274
275
  summary: GitLab metrics exporter
275
276
  test_files:
276
277
  - spec/cli_spec.rb
278
+ - spec/config_spec.rb
277
279
  - spec/database/bloat_spec.rb
278
280
  - spec/database/ci_builds_spec.rb
279
281
  - spec/database/geo_node_status_spec.rb