comap 1.0.0.pre.a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.gitlab-ci.yml +56 -0
- data/.kitchen.yml +125 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Berksfile +8 -0
- data/CONTRIBUTING.md +62 -0
- data/Dockerfile +15 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +327 -0
- data/LICENSE +202 -0
- data/README.md +65 -0
- data/Rakefile +32 -0
- data/bin/console +30 -0
- data/bin/setup +23 -0
- data/comap.gemspec +53 -0
- data/exe/comap +28 -0
- data/lib/comap.rb +100 -0
- data/lib/comap/app.rb +142 -0
- data/lib/comap/docker_client.rb +42 -0
- data/lib/comap/version.rb +21 -0
- data/metadata.rb +17 -0
- data/recipes/build_comap.rb +35 -0
- data/recipes/launch_comap.rb +66 -0
- metadata +183 -0
data/lib/comap/app.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'faraday'
|
19
|
+
require 'openssl'
|
20
|
+
|
21
|
+
# Main module
|
22
|
+
module COMAP
|
23
|
+
# Rack App
|
24
|
+
class App
|
25
|
+
# rubocop:disable Metrics/ParameterLists
|
26
|
+
def initialize(
|
27
|
+
docker_host: 'http://127.0.0.1',
|
28
|
+
docker_port: '2375',
|
29
|
+
docker_ssl: '',
|
30
|
+
metrics_path: '',
|
31
|
+
network: '',
|
32
|
+
services: []
|
33
|
+
)
|
34
|
+
raise ArgumentError, 'No services given' if services.empty?
|
35
|
+
raise ArgumentError, 'No network given' if network.nil?
|
36
|
+
ssl_hash = parse_ssl(docker_ssl)
|
37
|
+
@client = DockerClient.new(docker_host, docker_port, ssl_hash)
|
38
|
+
@network = network
|
39
|
+
@services = services
|
40
|
+
@metrics_path = metrics_path
|
41
|
+
error_msg = 'Could not retrieve Docker API version from '\
|
42
|
+
"#{docker_host}:#{docker_port} ssl:#{docker_ssl}"
|
43
|
+
raise StandardError, error_msg if @client.call('version').nil?
|
44
|
+
end
|
45
|
+
# rubocop:enable Metrics/ParameterLists
|
46
|
+
|
47
|
+
def call(_env)
|
48
|
+
['200', { 'Content-Type' => 'text/plain' }, [body]]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def body
|
54
|
+
@services.map do |s|
|
55
|
+
service, port = s.split(':')
|
56
|
+
service_metrics(service, port)
|
57
|
+
end.compact.map(&flat_hash).join
|
58
|
+
end
|
59
|
+
|
60
|
+
def service_metrics(service, port)
|
61
|
+
containers(service).sort_by { |c| c['slot'] }.map do |container|
|
62
|
+
next puts "#{service} service not found" if container.empty?
|
63
|
+
url = "http://#{container['ip']}:#{port}/#{@metrics_path}"
|
64
|
+
container_metrics(url, service, container['slot'])
|
65
|
+
end.reduce(&merge_proc)
|
66
|
+
end
|
67
|
+
|
68
|
+
def container_metrics(url, service, slot)
|
69
|
+
data = scrape_metrics(url, service)
|
70
|
+
return {} if data.nil?
|
71
|
+
data.lines.each_with_object({}) do |value, hash|
|
72
|
+
next hash[value] = nil if value.start_with?('#')
|
73
|
+
(hash[hash.keys.last] ||= []) << add_label(value, "#{service}.#{slot}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def scrape_metrics(url, service)
|
78
|
+
Faraday.get(url) do |req|
|
79
|
+
req.options.timeout = 2
|
80
|
+
req.options.open_timeout = 1
|
81
|
+
end.body
|
82
|
+
rescue StandardError
|
83
|
+
puts "Could not connect to #{service} endpoint #{url}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_label(line, label)
|
87
|
+
replace = %(container="#{label}")
|
88
|
+
line.gsub(/ /, "{#{replace}} ").gsub(/}{/, ',')
|
89
|
+
end
|
90
|
+
|
91
|
+
def containers(service)
|
92
|
+
running_tasks(service).map do |task|
|
93
|
+
{
|
94
|
+
'slot' => task['Slot'],
|
95
|
+
'ip' => ip(task).split('/').first
|
96
|
+
}
|
97
|
+
end.uniq
|
98
|
+
end
|
99
|
+
|
100
|
+
def running_tasks(service)
|
101
|
+
filters = {
|
102
|
+
'service' => { service => true },
|
103
|
+
'desired-state' => { 'running' => true }
|
104
|
+
}
|
105
|
+
@client.call('tasks', filters).keep_if do |task|
|
106
|
+
task['Status']['State'] == 'running'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def ip(task)
|
111
|
+
task['NetworksAttachments'].select do |h|
|
112
|
+
h['Network']['Spec']['Name'] == @network
|
113
|
+
end.first['Addresses'].first
|
114
|
+
end
|
115
|
+
|
116
|
+
# helpers
|
117
|
+
|
118
|
+
def parse_ssl(docker_ssl)
|
119
|
+
docker_ssl.split(',').map do |opt|
|
120
|
+
k, v = opt.split('=')
|
121
|
+
if k.include?('cert')
|
122
|
+
v = OpenSSL::X509::Certificate.new(File.read(v))
|
123
|
+
elsif k.include? 'key'
|
124
|
+
v = OpenSSL::PKey::RSA.new(File.read(v))
|
125
|
+
end
|
126
|
+
[k, v]
|
127
|
+
end.to_h
|
128
|
+
end
|
129
|
+
|
130
|
+
def merge_proc
|
131
|
+
proc do |old, new|
|
132
|
+
old.merge(new) { |_, value1, value2| value1 + value2 }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def flat_hash
|
137
|
+
proc do |item|
|
138
|
+
item.map { |key, value| "#{key}#{value.join}" }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'faraday'
|
19
|
+
require 'json'
|
20
|
+
|
21
|
+
# Module Docker
|
22
|
+
module COMAP
|
23
|
+
# Simple Engine API client
|
24
|
+
class DockerClient
|
25
|
+
def initialize(url, port, ssl = {})
|
26
|
+
@conn = Faraday.new(url: "#{url}:#{port}", ssl: ssl) do |faraday|
|
27
|
+
faraday.options.params_encoder = Faraday::FlatParamsEncoder
|
28
|
+
faraday.adapter Faraday.default_adapter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(route, filters = {})
|
33
|
+
response = @conn.get(route) do |req|
|
34
|
+
req.params['filters'] = filters.to_json
|
35
|
+
end
|
36
|
+
JSON.parse(response.body)
|
37
|
+
rescue StandardError => e
|
38
|
+
puts "Could not connect to Docker API #{@conn.url_prefix}"
|
39
|
+
puts e.message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
# Define version
|
19
|
+
module COMAP
|
20
|
+
VERSION = '1.0.0-a'
|
21
|
+
end
|
data/metadata.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
name 'comap-test'
|
4
|
+
maintainer 'Make.org'
|
5
|
+
maintainer_email 'sre@make.org'
|
6
|
+
license 'Apache-2.0'
|
7
|
+
description 'Integration tests for Comap'
|
8
|
+
long_description 'Integration tests for Comap'
|
9
|
+
source_url 'https://gitlab.com/sre-gems/comap'
|
10
|
+
issues_url 'https://gitlab.com/sre-gems/comap/issues'
|
11
|
+
version '1.0.0'
|
12
|
+
|
13
|
+
chef_version '>= 13.0'
|
14
|
+
|
15
|
+
supports 'centos', '>= 7.4'
|
16
|
+
|
17
|
+
depends 'docker-platform'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
# Build comap docker image and launch it
|
19
|
+
directory '/root/pkg'
|
20
|
+
|
21
|
+
cookbook_file '/root/Dockerfile' do
|
22
|
+
source 'Dockerfile'
|
23
|
+
notifies :run, 'execute[docker build comap]', :delayed
|
24
|
+
end
|
25
|
+
|
26
|
+
cookbook_file '/root/pkg/comap.gem' do
|
27
|
+
source 'comap.gem'
|
28
|
+
notifies :run, 'execute[docker build comap]', :delayed
|
29
|
+
end
|
30
|
+
|
31
|
+
execute 'docker build comap' do
|
32
|
+
command 'docker build . -t makeorg/comap'
|
33
|
+
cwd '/root'
|
34
|
+
action :nothing
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
# Create secrets
|
19
|
+
dir = '/root/.docker'
|
20
|
+
%w[ca.pem cert.pem key.pem].each do |secret|
|
21
|
+
execute "docker secret create #{secret} #{dir}/#{secret}" do
|
22
|
+
not_if "docker secret ls | grep #{secret}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
metrics_content = <<~CONTENT
|
27
|
+
# TYPE metrics_sample_seconds gauge
|
28
|
+
metrics_sample_seconds 42
|
29
|
+
# TYPE metrics_sample_2_seconds gauge
|
30
|
+
metrics_sample_2_seconds 24
|
31
|
+
CONTENT
|
32
|
+
|
33
|
+
docker_platform_service 'metrics' do
|
34
|
+
options(
|
35
|
+
mode: 'replicated',
|
36
|
+
replicas: 2,
|
37
|
+
network: 'kitchen',
|
38
|
+
publish: '5678:5678'
|
39
|
+
)
|
40
|
+
image 'hashicorp/http-echo'
|
41
|
+
command "-text '#{metrics_content.chomp("\n")}'"
|
42
|
+
action :create
|
43
|
+
end
|
44
|
+
|
45
|
+
server = 'https://comap-swarm-manager-centos-7'
|
46
|
+
ssl = {
|
47
|
+
'verify' => false, # https://github.com/berkshelf/berkshelf/issues/1458
|
48
|
+
'client_cert' => '/run/secrets/cert.pem',
|
49
|
+
'client_key' => '/run/secrets/key.pem',
|
50
|
+
'ca_file' => '/run/secrets/ca.pem'
|
51
|
+
}
|
52
|
+
ssl_opt = ssl.map { |k, v| "#{k}=#{v}" }.join(',')
|
53
|
+
puts "-H #{server} -p 2376 -s #{ssl_opt} -n kitchen metrics:5678"
|
54
|
+
|
55
|
+
# TODO: give options as an array in docker-platform
|
56
|
+
docker_platform_service 'comap' do
|
57
|
+
options(
|
58
|
+
mode: 'replicated',
|
59
|
+
network: 'kitchen',
|
60
|
+
publish: '9100:9100',
|
61
|
+
secret: %w[cert.pem key.pem ca.pem]
|
62
|
+
)
|
63
|
+
image 'makeorg/comap'
|
64
|
+
command "-H #{server} -p 2376 -s #{ssl_opt} -n kitchen metrics:5678"
|
65
|
+
action :create
|
66
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: comap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre.a
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sylvain Arrambourg
|
8
|
+
- Samuel Bernard
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-02-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.16'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.16'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '12.2'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '12.2'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rubocop
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.51'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.51'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: webmock
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: faraday
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.13'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.13'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: json
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '2.0'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '2.0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: thin
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '1.7'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.7'
|
126
|
+
description: Container Orchestrator Metrics Aggregator for Prometheus
|
127
|
+
email:
|
128
|
+
- sar@make.org
|
129
|
+
executables:
|
130
|
+
- comap
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- ".gitignore"
|
135
|
+
- ".gitlab-ci.yml"
|
136
|
+
- ".kitchen.yml"
|
137
|
+
- ".rspec"
|
138
|
+
- ".ruby-version"
|
139
|
+
- ".travis.yml"
|
140
|
+
- Berksfile
|
141
|
+
- CONTRIBUTING.md
|
142
|
+
- Dockerfile
|
143
|
+
- Gemfile
|
144
|
+
- Gemfile.lock
|
145
|
+
- LICENSE
|
146
|
+
- README.md
|
147
|
+
- Rakefile
|
148
|
+
- bin/console
|
149
|
+
- bin/setup
|
150
|
+
- comap.gemspec
|
151
|
+
- exe/comap
|
152
|
+
- lib/comap.rb
|
153
|
+
- lib/comap/app.rb
|
154
|
+
- lib/comap/docker_client.rb
|
155
|
+
- lib/comap/version.rb
|
156
|
+
- metadata.rb
|
157
|
+
- recipes/build_comap.rb
|
158
|
+
- recipes/launch_comap.rb
|
159
|
+
homepage: https://gitlab.com/sre-gems/comap
|
160
|
+
licenses:
|
161
|
+
- Apache-2.0
|
162
|
+
metadata: {}
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 1.3.1
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 2.7.3
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Container Orchestrator Metrics Aggregator for Prometheus
|
183
|
+
test_files: []
|