@cryptotaxi247 / netdata-1 / commits / 954f91e50

go.d: local-listeners sd: use "ip:port" as address instead of "localhost" (#17203)

Ilya Mashchenko committed Mar 20, 2024 at 12:25 UTC 954f91e504c7660ea4e1e1763afe9e4de89c404a
5 files changed +171 -124
src/go/collectors/go.d.plugin/agent/discovery/sd/discoverer/netlisteners/netlisteners.go
+37 -7
@@ -9,6 +9,7 @@ import (
9 "errors"
10 "fmt"
11 "log/slog"
12 + "net"
13 "os"
14 "os/exec"
15 "path/filepath"
@@ -183,27 +184,56 @@ func (d *Discoverer) processTargets(tgts []model.Target) []model.TargetGroup {
184
185 func (d *Discoverer) parseLocalListeners(bs []byte) ([]model.Target, error) {
186 var tgts []model.Target
186 -
187 + set := make(map[string]bool)
188 sc := bufio.NewScanner(bytes.NewReader(bs))
189 +
190 for sc.Scan() {
191 text := strings.TrimSpace(sc.Text())
192 if text == "" {
193 continue
194 }
195
194 - // Protocol|Address|Port|Cmdline
196 + // Protocol|IPAddress|Port|Cmdline
197 parts := strings.SplitN(text, "|", 4)
198 if len(parts) != 4 {
199 return nil, fmt.Errorf("unexpected data: '%s'", text)
200 }
201
202 tgt := target{
201 - Protocol: parts[0],
202 - Address: parts[1],
203 - Port: parts[2],
204 - Comm: extractComm(parts[3]),
205 - Cmdline: parts[3],
203 + Protocol: parts[0],
204 + IPAddress: parts[1],
205 + Port: parts[2],
206 + Comm: extractComm(parts[3]),
207 + Cmdline: parts[3],
208 + }
209 +
210 + const (
211 + local4 = "127.0.0.1"
212 + local6 = "::1"
213 + )
214 +
215 + if tgt.IPAddress == "0.0.0.0" || strings.HasPrefix(tgt.IPAddress, "127") {
216 + tgt.IPAddress = local4
217 + } else if tgt.IPAddress == "::" {
218 + tgt.IPAddress = local6
219 + }
220 +
221 + tgt.Address = net.JoinHostPort(tgt.IPAddress, tgt.Port)
222 +
223 + key := fmt.Sprintf("%s:%s", tgt.Protocol, tgt.Address)
224 + var keyLocal string
225 + if strings.HasSuffix(tgt.Protocol, "6") {
226 + keyLocal = fmt.Sprintf("%s:%s", tgt.Protocol, net.JoinHostPort(local6, tgt.Port))
227 + } else {
228 + keyLocal = fmt.Sprintf("%s:%s", tgt.Protocol, net.JoinHostPort(local4, tgt.Port))
229 + }
230 +
231 + // Filter targets that accept conns on any (0.0.0.0) and additionally on each individual network interface (a.b.c.d).
232 + // Create a target only for localhost. Assumption: any address always goes first.
233 + if set[key] || set[keyLocal] {
234 + continue
235 }
236 + set[key] = true
237
238 hash, err := calcHash(tgt)
239 if err != nil {
src/go/collectors/go.d.plugin/agent/discovery/sd/discoverer/netlisteners/netlisteners_test.go
+64 -50
@@ -15,7 +15,11 @@ func TestDiscoverer_Discover(t *testing.T) {
15 listenersCli: func(cli listenersCli, interval, expiry time.Duration) {
16 cli.addListener("UDP6|::1|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
17 cli.addListener("TCP6|::1|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
18 + cli.addListener("TCP6|::|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
19 + cli.addListener("TCP6|2001:DB8::1|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
20 cli.addListener("TCP|127.0.0.1|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
21 + cli.addListener("TCP|0.0.0.0|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
22 + cli.addListener("TCP|192.0.2.1|8125|/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D")
23 cli.addListener("UDP|127.0.0.1|53768|/opt/netdata/usr/libexec/netdata/plugins.d/go.d.plugin 1")
24 time.Sleep(interval * 2)
25 },
@@ -24,32 +28,36 @@ func TestDiscoverer_Discover(t *testing.T) {
28 source: "discoverer=net_listeners,host=localhost",
29 targets: []model.Target{
30 withHash(&target{
27 - Protocol: "UDP6",
28 - Address: "::1",
29 - Port: "8125",
30 - Comm: "netdata",
31 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
31 + Protocol: "UDP6",
32 + IPAddress: "::1",
33 + Port: "8125",
34 + Address: "[::1]:8125",
35 + Comm: "netdata",
36 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
37 }),
38 withHash(&target{
34 - Protocol: "TCP6",
35 - Address: "::1",
36 - Port: "8125",
37 - Comm: "netdata",
38 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
39 + Protocol: "TCP6",
40 + IPAddress: "::1",
41 + Port: "8125",
42 + Address: "[::1]:8125",
43 + Comm: "netdata",
44 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
45 }),
46 withHash(&target{
41 - Protocol: "TCP",
42 - Address: "127.0.0.1",
43 - Port: "8125",
44 - Comm: "netdata",
45 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
47 + Protocol: "TCP",
48 + IPAddress: "127.0.0.1",
49 + Port: "8125",
50 + Address: "127.0.0.1:8125",
51 + Comm: "netdata",
52 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
53 }),
54 withHash(&target{
48 - Protocol: "UDP",
49 - Address: "127.0.0.1",
50 - Port: "53768",
51 - Comm: "go.d.plugin",
52 - Cmdline: "/opt/netdata/usr/libexec/netdata/plugins.d/go.d.plugin 1",
55 + Protocol: "UDP",
56 + IPAddress: "127.0.0.1",
57 + Port: "53768",
58 + Address: "127.0.0.1:53768",
59 + Comm: "go.d.plugin",
60 + Cmdline: "/opt/netdata/usr/libexec/netdata/plugins.d/go.d.plugin 1",
61 }),
62 },
63 }},
@@ -70,32 +78,36 @@ func TestDiscoverer_Discover(t *testing.T) {
78 source: "discoverer=net_listeners,host=localhost",
79 targets: []model.Target{
80 withHash(&target{
73 - Protocol: "UDP6",
74 - Address: "::1",
75 - Port: "8125",
76 - Comm: "netdata",
77 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
81 + Protocol: "UDP6",
82 + IPAddress: "::1",
83 + Port: "8125",
84 + Address: "[::1]:8125",
85 + Comm: "netdata",
86 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
87 }),
88 withHash(&target{
80 - Protocol: "TCP6",
81 - Address: "::1",
82 - Port: "8125",
83 - Comm: "netdata",
84 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
89 + Protocol: "TCP6",
90 + IPAddress: "::1",
91 + Port: "8125",
92 + Address: "[::1]:8125",
93 + Comm: "netdata",
94 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
95 }),
96 withHash(&target{
87 - Protocol: "TCP",
88 - Address: "127.0.0.1",
89 - Port: "8125",
90 - Comm: "netdata",
91 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
97 + Protocol: "TCP",
98 + IPAddress: "127.0.0.1",
99 + Port: "8125",
100 + Address: "127.0.0.1:8125",
101 + Comm: "netdata",
102 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
103 }),
104 withHash(&target{
94 - Protocol: "UDP",
95 - Address: "127.0.0.1",
96 - Port: "53768",
97 - Comm: "go.d.plugin",
98 - Cmdline: "/opt/netdata/usr/libexec/netdata/plugins.d/go.d.plugin 1",
105 + Protocol: "UDP",
106 + IPAddress: "127.0.0.1",
107 + Port: "53768",
108 + Address: "127.0.0.1:53768",
109 + Comm: "go.d.plugin",
110 + Cmdline: "/opt/netdata/usr/libexec/netdata/plugins.d/go.d.plugin 1",
111 }),
112 },
113 }},
@@ -116,18 +128,20 @@ func TestDiscoverer_Discover(t *testing.T) {
128 source: "discoverer=net_listeners,host=localhost",
129 targets: []model.Target{
130 withHash(&target{
119 - Protocol: "TCP6",
120 - Address: "::1",
121 - Port: "8125",
122 - Comm: "netdata",
123 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
131 + Protocol: "TCP6",
132 + IPAddress: "::1",
133 + Port: "8125",
134 + Address: "[::1]:8125",
135 + Comm: "netdata",
136 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
137 }),
138 withHash(&target{
126 - Protocol: "TCP",
127 - Address: "127.0.0.1",
128 - Port: "8125",
129 - Comm: "netdata",
130 - Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
139 + Protocol: "TCP",
140 + IPAddress: "127.0.0.1",
141 + Port: "8125",
142 + Address: "127.0.0.1:8125",
143 + Comm: "netdata",
144 + Cmdline: "/opt/netdata/usr/sbin/netdata -P /run/netdata/netdata.pid -D",
145 }),
146 },
147 }},
src/go/collectors/go.d.plugin/agent/discovery/sd/discoverer/netlisteners/sim_test.go
+8 -7
@@ -5,6 +5,7 @@ package netlisteners
5 import (
6 "context"
7 "errors"
8 + "slices"
9 "sort"
10 "strings"
11 "sync"
@@ -103,29 +104,29 @@ func (sim *discoverySim) run(t *testing.T) {
104 }
105
106 func newMockLocalListenersExec() *mockLocalListenersExec {
106 - return &mockLocalListenersExec{
107 - listeners: make(map[string]bool),
108 - }
107 + return &mockLocalListenersExec{}
108 }
109
110 type mockLocalListenersExec struct {
111 errResponse bool
112 mux sync.Mutex
114 - listeners map[string]bool
113 + listeners []string
114 }
115
116 func (m *mockLocalListenersExec) addListener(s string) {
117 m.mux.Lock()
118 defer m.mux.Unlock()
119
121 - m.listeners[s] = true
120 + m.listeners = append(m.listeners, s)
121 }
122
123 func (m *mockLocalListenersExec) removeListener(s string) {
124 m.mux.Lock()
125 defer m.mux.Unlock()
126
128 - delete(m.listeners, s)
127 + if i := slices.Index(m.listeners, s); i != -1 {
128 + m.listeners = append(m.listeners[:i], m.listeners[i+1:]...)
129 + }
130 }
131
132 func (m *mockLocalListenersExec) discover(context.Context) ([]byte, error) {
@@ -137,7 +138,7 @@ func (m *mockLocalListenersExec) discover(context.Context) ([]byte, error) {
138 defer m.mux.Unlock()
139
140 var buf strings.Builder
140 - for s := range m.listeners {
141 + for _, s := range m.listeners {
142 buf.WriteString(s)
143 buf.WriteByte('\n')
144 }
src/go/collectors/go.d.plugin/agent/discovery/sd/discoverer/netlisteners/target.go
+7 -5
@@ -24,11 +24,13 @@ type target struct {
24
25 hash uint64
26
27 - Protocol string
28 - Address string
29 - Port string
30 - Comm string
31 - Cmdline string
27 + Protocol string
28 + IPAddress string
29 + Port string
30 + Comm string
31 + Cmdline string
32 +
33 + Address string // "IPAddress:Port"
34 }
35
36 func (t *target) TUID() string { return tuid(t) }
src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf
+55 -55
@@ -35,7 +35,7 @@ classify:
35 - tags: "dnsdist"
36 expr: '{{ and (eq .Port "8083") (eq .Comm "dnsdist") }}'
37 - tags: "dnsmasq"
38 - expr: '{{ and (eq .Port "53") (eq .Comm "dnsmasq") }}'
38 + expr: '{{ and (eq .Protocol "UDP") (eq .Port "53") (eq .Comm "dnsmasq") }}'
39 - tags: "docker_engine"
40 expr: '{{ and (eq .Port "9323") (eq .Comm "dockerd") }}'
41 - tags: "elasticsearch"
@@ -111,7 +111,7 @@ classify:
111 tags: "-unknown exporter"
112 match:
113 - tags: "exporter"
114 - expr: '{{ not (empty (promPort .Port)) }}'
114 + expr: '{{ and (not (empty (promPort .Port))) (not (eq .Comm "docker-proxy")) }}'
115 compose:
116 - name: "Applications"
117 selector: "app"
@@ -120,59 +120,59 @@ compose:
120 template: |
121 module: activemq
122 name: local
123 - url: http://localhost:{{.Port}}
123 + url: http://{{.Address}}
124 webadmin: admin
125 - selector: "apache"
126 template: |
127 module: apache
128 name: local
129 - url: http://localhost:{{.Port}}/server-status?auto
129 + url: http://{{.Address}}/server-status?auto
130 - selector: "bind"
131 template: |
132 module: bind
133 name: local
134 - url: http://localhost:{{.Port}}/json/v1
134 + url: http://{{.Address}}/json/v1
135 - selector: "cassandra"
136 template: |
137 module: cassandra
138 name: local
139 - url: http://localhost:{{.Port}}/metrics
139 + url: http://{{.Address}}/metrics
140 - selector: "chrony"
141 template: |
142 module: chrony
143 name: local
144 - address: 127.0.0.1:{{.Port}}
144 + address: {{.Address}}
145 - selector: "cockroachdb"
146 template: |
147 module: cockroachdb
148 name: local
149 - url: http://localhost:{{.Port}}/_status/vars
149 + url: http://{{.Address}}/_status/vars
150 - selector: "consul"
151 template: |
152 module: consul
153 name: local
154 - url: http://localhost:{{.Port}}
154 + url: http://{{.Address}}
155 - selector: "coredns"
156 template: |
157 module: coredns
158 name: local
159 - url: http://localhost:{{.Port}}/metrics
159 + url: http://{{.Address}}/metrics
160 - selector: "couchbase"
161 template: |
162 module: couchbase
163 name: local
164 - url: http://localhost:{{.Port}}
164 + url: http://{{.Address}}
165 - selector: "couchdb"
166 template: |
167 module: couchdb
168 name: local
169 - url: http://localhost:{{.Port}}
169 + url: http://{{.Address}}
170 node: '_local'
171 - selector: "dnsdist"
172 template: |
173 module: dnsdist
174 name: local
175 - url: http://localhost:{{.Port}}
175 + url: http://{{.Address}}
176 headers:
177 X-API-Key: 'dnsdist-api-key'
178 - selector: "dnsmasq"
@@ -180,23 +180,23 @@ compose:
180 module: dnsmasq
181 name: local
182 protocol: udp
183 - address: 127.0.0.1:{{.Port}}
183 + address: {{.Address}}
184 - selector: "docker_engine"
185 template: |
186 module: docker_engine
187 name: local
188 - url: http://localhost:{{.Port}}/metrics
188 + url: http://{{.Address}}/metrics
189 - selector: "elasticsearch"
190 template: |
191 module: elasticsearch
192 name: local
193 - url: http://localhost:{{.Port}}
193 + url: http://{{.Address}}
194 cluster_mode: no
195 - selector: "opensearch"
196 template: |
197 module: elasticsearch
198 name: local
199 - url: https://localhost:{{.Port}}
199 + url: https://{{.Address}}
200 cluster_mode: no
201 tls_skip_verify: yes
202 username: admin
@@ -205,148 +205,148 @@ compose:
205 template: |
206 module: envoy
207 name: local
208 - url: http://localhost:{{.Port}}/stats/prometheus
208 + url: http://{{.Address}}/stats/prometheus
209 - selector: "envoy"
210 template: |
211 module: envoy
212 name: local
213 - url: http://localhost:{{.Port}}/stats/prometheus
213 + url: http://{{.Address}}/stats/prometheus
214 - selector: "fluentd"
215 template: |
216 module: fluentd
217 name: local
218 - url: http://localhost:{{.Port}}
218 + url: http://{{.Address}}
219 - selector: "freeradius"
220 template: |
221 module: freeradius
222 name: local
223 - address: localhost
223 + address: {{.IPAddress}}
224 port: {{.Port}}
225 secret: adminsecret
226 - selector: "geth"
227 template: |
228 module: geth
229 name: local
230 - url: http://localhost:{{.Port}}/debug/metrics/prometheus
230 + url: http://{{.Address}}/debug/metrics/prometheus
231 - selector: "haproxy"
232 template: |
233 module: haproxy
234 name: local
235 - url: http://localhost:{{.Port}}/metrics
235 + url: http://{{.Address}}/metrics
236 - selector: "hdfs_namenode"
237 template: |
238 module: hdfs
239 name: namenode_local
240 - url: http://localhost:{{.Port}}/jmx
240 + url: http://{{.Address}}/jmx
241 - selector: "hdfs_datanode"
242 template: |
243 module: hdfs
244 name: datanode_local
245 - url: http://localhost:{{.Port}}/jmx
245 + url: http://{{.Address}}/jmx
246 - selector: "kubelet"
247 template: |
248 module: kubelet
249 name: local
250 {{- if eq .Port "10255" }}
251 - url: http://localhost:{{.Port}}/metrics
251 + url: http://{{.Address}}/metrics
252 {{- else }}
253 - url: https://localhost:{{.Port}}/metrics
253 + url: https://{{.Address}}/metrics
254 tls_skip_verify: yes
255 {{- end }}
256 - selector: "kubeproxy"
257 template: |
258 module: kubeproxy
259 name: local
260 - url: http://localhost:{{.Port}}/metrics
260 + url: http://{{.Address}}/metrics
261 - selector: "lighttpd"
262 template: |
263 module: lighttpd
264 name: local
265 - url: http://localhost:{{.Port}}/server-status?auto
265 + url: http://{{.Address}}/server-status?auto
266 - selector: "logstash"
267 template: |
268 module: logstash
269 name: local
270 - url: http://localhost:{{.Port}}
270 + url: http://{{.Address}}
271 - selector: "mongodb"
272 template: |
273 module: mongodb
274 name: local
275 - uri: mongodb://localhost:{{.Port}}
275 + uri: mongodb://{{.Address}}
276 - selector: "mysql"
277 template: |
278 module: mysql
279 name: local
280 - dsn: netdata@tcp(127.0.0.1:{{.Port}})/
280 + dsn: netdata@tcp({{.IPAddress}}:{{.Port}})/
281 - selector: "nginx"
282 template: |
283 - module: nginx
284 name: local
285 - url: http://localhost:{{.Port}}/stub_status
285 + url: http://{{.Address}}/stub_status
286 - module: nginx
287 name: local
288 - url: http://localhost:{{.Port}}/basic_status
288 + url: http://{{.Address}}/basic_status
289 - module: nginx
290 name: local
291 - url: http://localhost:{{.Port}}/nginx_status
291 + url: http://{{.Address}}/nginx_status
292 - module: nginx
293 name: local
294 - url: http://localhost:{{.Port}}/status
294 + url: http://{{.Address}}/status
295 - selector: "ntpd"
296 template: |
297 module: ntpd
298 name: local
299 - address: 127.0.0.1:{{.Port}}
299 + address: {{.Address}}
300 collect_peers: no
301 - selector: "openvpn"
302 template: |
303 module: openvpn
304 name: local
305 - address: 127.0.0.1:{{.Port}}
305 + address: {{.Address}}
306 - selector: "pgbouncer"
307 template: |
308 module: pgbouncer
309 name: local
310 - dsn: postgres://netdata:postgres@127.0.0.1:{{.Port}}/pgbouncer
310 + dsn: postgres://netdata:postgres@{{.IPAddress}}:{{.Port}}/pgbouncer
311 - selector: "pihole"
312 template: |
313 module: pihole
314 name: local
315 - url: http://localhost
315 + url: http://{{.Address}}
316 - selector: "pika"
317 template: |
318 module: pika
319 name: local
320 - address: redis://@127.0.0.1:{{.Port}}
320 + address: redis://@{{.IPAddress}}:{{.Port}}
321 - selector: "postgres"
322 template: |
323 module: postgres
324 name: local
325 - dsn: postgresql://netdata@127.0.0.1:{{.Port}}/postgres
325 + dsn: postgresql://netdata@{{.IPAddress}}:{{.Port}}/postgres
326 - selector: "powerdns"
327 template: |
328 module: powerdns
329 name: local
330 - url: http://localhost:{{.Port}}
330 + url: http://{{.Address}}
331 headers:
332 X-API-KEY: secret
333 - selector: "powerdns_recursor"
334 template: |
335 module: powerdns_recursor
336 name: local
337 - url: http://localhost:{{.Port}}
337 + url: http://{{.Address}}
338 headers:
339 X-API-KEY: secret
340 - selector: "proxysql"
341 template: |
342 module: proxysql
343 name: local
344 - dsn: stats:stats@tcp(127.0.0.1:{{.Port}})/
344 + dsn: stats:stats@tcp({{.IPAddress}}:{{.Port}})/
345 - selector: "rabbitmq"
346 template: |
347 module: rabbitmq
348 name: local
349 - url: http://localhost:{{.Port}}
349 + url: http://{{.Address}}
350 username: guest
351 password: guest
352 collect_queues_metrics: no
@@ -354,42 +354,42 @@ compose:
354 template: |
355 module: redis
356 name: local
357 - address: redis://@127.0.0.1:{{.Port}}
357 + address: redis://@{{.IPAddress}}:{{.Port}}
358 - selector: "supervisord"
359 template: |
360 module: supervisord
361 name: local
362 - url: http://localhost:{{.Port}}/RPC2
362 + url: http://{{.Address}}/RPC2
363 - selector: "traefik"
364 template: |
365 module: traefik
366 name: local
367 - url: http://localhost:{{.Port}}/metrics
367 + url: http://{{.Address}}/metrics
368 - selector: "traefik"
369 template: |
370 module: traefik
371 name: local
372 - url: http://localhost:{{.Port}}/metrics
372 + url: http://{{.Address}}/metrics
373 - selector: "unbound"
374 template: |
375 module: unbound
376 name: local
377 - address: 127.0.0.1:{{.Port}}
377 + address: {{.Address}}
378 - selector: "upsd"
379 template: |
380 module: upsd
381 name: local
382 - address: 127.0.0.1:{{.Port}}
382 + address: {{.Address}}
383 - selector: "vernemq"
384 template: |
385 module: vernemq
386 name: local
387 - url: http://localhost:{{.Port}}/metrics
387 + url: http://{{.Address}}/metrics
388 - selector: "zookeeper"
389 template: |
390 module: zookeeper
391 name: local
392 - address: 127.0.0.1:{{.Port}}
392 + address: {{.Address}}
393
394 - name: "Prometheus exporters generic"
395 selector: "exporter"
@@ -399,7 +399,7 @@ compose:
399 {{ $name := promPort .Port -}}
400 module: prometheus
401 name: {{$name}}_local
402 - url: http://localhost:{{.Port}}/metrics
402 + url: http://{{.Address}}/metrics
403 {{ if eq $name "caddy" -}}
404 expected_prefix: 'caddy_'
405 {{ else if eq $name "openethereum" -}}