1#![forbid(unsafe_code)]
2#![recursion_limit = "512"]
3use pharos_corefile::{BlockContext, Cursor, Directive, Error};
8use pharos_server::{Lifecycle, ListenerKey, Router, Server, Transport};
9use std::{
10 collections::HashMap,
11 io,
12 net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6},
13 sync::Arc,
14};
15
16#[cfg(feature = "plugin-any")]
17pub mod any;
18#[cfg(feature = "plugin-bind")]
19pub mod bind;
20#[cfg(feature = "plugin-bufsize")]
21pub mod bufsize;
22#[cfg(feature = "plugin-cache")]
23pub mod cache;
24#[cfg(feature = "plugin-chaos")]
25pub mod chaos;
26#[cfg(feature = "plugin-errors")]
27pub mod errors;
28#[cfg(feature = "plugin-header")]
29pub mod header;
30#[cfg(any(
31 feature = "plugin-health",
32 feature = "plugin-errors",
33 feature = "plugin-reload",
34 feature = "plugin-hosts",
35 feature = "plugin-loadbalance",
36 feature = "plugin-kubernetes"
37))]
38pub mod health;
39#[cfg(feature = "plugin-hosts")]
40pub mod hosts;
41#[cfg(feature = "plugin-kubernetes")]
42pub mod kubernetes;
43#[cfg(feature = "plugin-loadbalance")]
44pub mod loadbalance;
45#[cfg(feature = "plugin-local")]
46pub mod local;
47#[cfg(feature = "plugin-minimal")]
48pub mod minimal;
49#[cfg(feature = "plugin-nsid")]
50pub mod nsid;
51pub mod proxy;
52
53#[cfg(all(test, feature = "plugin-cache", feature = "plugin-hosts"))]
54mod self_lookup_test;
55
56#[cfg(feature = "plugin-forward")]
57pub mod forward;
58
59#[cfg(feature = "plugin-log")]
60pub mod log;
61#[cfg(feature = "plugin-log")]
62pub mod log_format;
63#[cfg(feature = "plugin-loop")]
64pub mod r#loop;
65#[cfg(feature = "plugin-metadata")]
66pub mod metadata;
67#[cfg(feature = "plugin-prometheus")]
68pub mod prometheus;
69#[cfg(feature = "plugin-ready")]
70pub mod ready;
71#[cfg(feature = "plugin-reload")]
72pub mod reload;
73#[cfg(feature = "plugin-whoami")]
74pub mod whoami;
75
76include!(concat!(env!("OUT_DIR"), "/plugins.rs"));
77
78#[cfg(any(
79 feature = "plugin-health",
80 feature = "plugin-prometheus",
81 feature = "plugin-ready"
82))]
83fn valid_http_address(address: &str) -> bool {
84 let Some((host, port)) = address.rsplit_once(':') else {
85 return false;
86 };
87 if port.contains(['[', ']']) {
88 return false;
89 }
90 if host.starts_with('[') {
91 host.ends_with(']') && !host[1..host.len() - 1].contains(['[', ']'])
92 } else {
93 !host.contains([':', '[', ']'])
94 }
95}
96
97#[must_use]
99pub fn directives() -> &'static [Directive<'static, Chain>] {
100 DIRECTIVES
101}
102
103#[must_use]
105pub const fn plugins_listing() -> &'static str {
106 PLUGINS_LISTING
107}
108
109pub type Servers = HashMap<ListenerKey, Server<Arc<Chain>>>;
111
112pub fn servers(blocks: Vec<BlockContext<Chain>>) -> io::Result<(Servers, Lifecycle)> {
115 servers_with_registry(blocks, pharos_server::metrics::Registry::default())
116}
117
118pub fn servers_with_registry(
120 blocks: Vec<BlockContext<Chain>>,
121 metrics: pharos_server::metrics::Registry,
122) -> io::Result<(Servers, Lifecycle)> {
123 type ListenerGroup = (Router<Arc<Chain>>, Option<usize>, bool);
124 let mut groups: HashMap<ListenerKey, ListenerGroup> = HashMap::new();
125 let mut lifecycle = Lifecycle::default();
126 #[cfg(feature = "plugin-forward")]
127 let _ = metrics.forward();
128 #[cfg(feature = "plugin-local")]
129 let _ = metrics.local();
130 #[cfg(feature = "plugin-health")]
131 metrics.enable_health_metrics();
132 #[cfg(feature = "plugin-prometheus")]
133 let mut exporters = std::collections::BTreeMap::new();
134 #[cfg(feature = "plugin-ready")]
135 let ready_state = Arc::new(ready::State::default());
136 #[cfg(feature = "plugin-ready")]
137 let mut ready_addresses = std::collections::BTreeSet::new();
138 #[cfg(feature = "plugin-health")]
139 let mut health_addresses: Vec<(String, std::time::Duration)> = Vec::new();
140 #[cfg(feature = "plugin-ready")]
141 {
142 let state = Arc::clone(&ready_state);
143 lifecycle.register_sync(pharos_server::Phase::Shutdown, move || {
145 state.begin_shutdown();
146 Ok(())
147 });
148 }
149 for block in blocks {
150 #[cfg(any(
151 feature = "plugin-forward",
152 feature = "plugin-cache",
153 feature = "plugin-local"
154 ))]
155 let mut block = block;
156 #[cfg(feature = "plugin-forward")]
157 if let Some(forward) = &mut block.config.forward {
158 forward.set_metrics(&metrics);
159 }
160 #[cfg(feature = "plugin-local")]
161 if let Some(local) = &mut block.config.local {
162 local.set_metrics(&metrics);
163 }
164 #[cfg(feature = "plugin-hosts")]
165 if let Some(hosts) = &block.config.hosts {
166 hosts.set_metrics(&metrics);
167 }
168 #[cfg(feature = "plugin-kubernetes")]
169 if let Some(kubernetes) = &block.config.kubernetes {
170 kubernetes.set_metrics(&metrics);
171 }
172 #[cfg(feature = "plugin-health")]
173 if let Some(health) = &block.config.health
174 && !health_addresses
175 .iter()
176 .any(|(address, _)| *address == health.address)
177 {
178 health_addresses.push((health.address.clone(), health.lameduck));
179 }
180 #[cfg(feature = "plugin-ready")]
181 if let Some(ready) = &block.config.ready {
182 ready_addresses.insert(ready.address.clone());
183 ready_state.set_continuous(ready.continuous);
184 }
185 #[cfg(feature = "plugin-prometheus")]
186 if let Some(prometheus) = &block.config.prometheus {
187 if exporters
188 .get(&prometheus.address)
189 .is_some_and(|config| config != &prometheus.tls_config)
190 {
191 return Err(io::Error::new(
192 io::ErrorKind::InvalidInput,
193 "conflicting prometheus TLS configuration",
194 ));
195 }
196 exporters
197 .entry(prometheus.address.clone())
198 .or_insert_with(|| prometheus.tls_config.clone());
199 }
200 let keys = block.keys().to_vec();
201 let addresses = block.listen_addresses.clone();
202 let empty = pharos_server::Chain::is_empty(&block.config);
203 lifecycle.extend(&block.config.lifecycle);
204 let mut routes = Vec::new();
205 for key in keys {
206 if key.transport != "dns" {
207 return Err(io::Error::new(
208 io::ErrorKind::InvalidInput,
209 "unsupported Listener transport",
210 ));
211 }
212 let port: u16 = key.port.parse().map_err(|_| {
213 io::Error::new(io::ErrorKind::InvalidInput, "invalid Listener port")
214 })?;
215 let domain: pharos_proto::Name = key.domain.parse().map_err(|_| {
216 io::Error::new(io::ErrorKind::InvalidInput, "invalid Listener Domain")
217 })?;
218 for address in &addresses {
219 let socket = if address.is_empty() {
220 SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port)
221 } else if let Some((ip, interface)) = address.split_once('%') {
222 let ip: Ipv6Addr = ip.parse().map_err(|_| {
223 io::Error::new(io::ErrorKind::InvalidInput, "invalid Listener address")
224 })?;
225 let scope = nix::net::if_::if_nametoindex(interface)
227 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
228 SocketAddr::V6(SocketAddrV6::new(ip, port, 0, scope))
229 } else {
230 let ip: IpAddr = address.parse().map_err(|_| {
231 io::Error::new(io::ErrorKind::InvalidInput, "invalid Listener address")
232 })?;
233 let ip = match ip {
234 IpAddr::V6(ip) => ip.to_ipv4_mapped().map_or(IpAddr::V6(ip), IpAddr::V4),
235 ip => ip,
236 };
237 SocketAddr::new(ip, port)
238 };
239 routes.push((
240 ListenerKey {
241 transport: Transport::Dns,
242 address: socket,
243 },
244 domain.clone(),
245 ));
246 }
247 }
248 #[cfg(feature = "plugin-loop")]
249 if let Some(plugin) = &block.config.r#loop
250 && let Some((listener, _)) = routes.first()
251 {
252 plugin.register(&mut lifecycle, listener.address);
253 }
254 #[cfg(feature = "plugin-cache")]
255 if let Some(cache) = &mut block.config.cache {
256 let listeners = routes.iter().map(|(key, _)| *key).collect::<Vec<_>>();
257 cache.set_metrics(&metrics, &listeners);
258 }
259 #[cfg(feature = "plugin-multisocket")]
260 let workers = block.config.multisocket.flatten();
261 #[cfg(not(feature = "plugin-multisocket"))]
262 let workers = None;
263 let chain = Arc::new(block.config);
264 #[cfg(feature = "plugin-chaos")]
265 let allow_non_in = chain.chaos.is_some();
266 #[cfg(not(feature = "plugin-chaos"))]
267 let allow_non_in = false;
268 #[cfg(feature = "plugin-cache")]
269 if let Some(cache) = &chain.cache {
270 cache.bind(&chain);
271 }
272 #[cfg(feature = "plugin-forward")]
273 if chain.forward.is_some() {
274 let chain = Arc::clone(&chain);
275 lifecycle.register_sync(pharos_server::Phase::Shutdown, move || {
276 if let Some(forward) = &chain.forward {
277 forward.stop();
278 }
279 Ok(())
280 });
281 }
282 #[cfg(feature = "plugin-ready")]
283 if chain.ready.is_some() {
284 chain.collect_readiness(&ready_state);
285 }
286 for (listener, domain) in routes {
287 let group = groups
288 .entry(listener)
289 .or_insert_with(|| (Router::default(), workers, false));
290 group.2 |= allow_non_in;
291 group.0.add(
292 listener,
293 domain,
294 (!empty).then(|| Arc::clone(&chain)),
295 vec![],
296 );
297 }
298 }
299 let servers = groups
300 .into_iter()
301 .map(|(key, (router, workers, allow_non_in))| {
302 let mut server = Server::with_registry(router, key, metrics.clone());
303 server.allow_non_in = allow_non_in;
304 if let Some(workers) = workers {
305 server.udp_workers = workers;
306 }
307 (key, server)
308 })
309 .collect();
310 #[cfg(feature = "plugin-health")]
311 for (address, lameduck) in health_addresses {
312 let listener = Arc::new(std::sync::Mutex::new(
313 None::<(
314 pharos_server::http::Listener,
315 pharos_server::http::HealthProbe,
316 )>,
317 ));
318 let startup_listener = Arc::clone(&listener);
319 let registry = metrics.clone();
320 let start = move || {
321 let address = address.clone();
322 let listener = Arc::clone(&startup_listener);
323 let registry = registry.clone();
324 async move {
325 if listener.lock().unwrap().is_some() {
326 return Ok(());
327 }
328 let server =
329 pharos_server::http::Listener::bind_with_methods(&address, |_, path| {
330 Some(if path == "/health" {
331 (200, "text/plain; charset=utf-8", "OK".into())
332 } else {
333 (
334 404,
335 "text/plain; charset=utf-8",
336 "404 page not found\n".into(),
337 )
338 })
339 })
340 .await?;
341 let probe = pharos_server::http::HealthProbe::start(&address, registry);
342 *listener.lock().unwrap() = Some((server, probe));
343 Ok(())
344 }
345 };
346 lifecycle.register(pharos_server::Phase::Startup, start.clone());
347 lifecycle.register(pharos_server::Phase::RestartFailed, start);
348 let restart_listener = Arc::clone(&listener);
349 lifecycle.register(pharos_server::Phase::Restart, move || {
350 let server = restart_listener.lock().unwrap().take();
351 async move {
352 if let Some((server, probe)) = server {
353 drop(probe);
354 server.shutdown().await?;
355 }
356 Ok(())
357 }
358 });
359 lifecycle.register(pharos_server::Phase::Shutdown, move || {
360 let server = listener.lock().unwrap().take();
361 async move {
362 if let Some((server, probe)) = server {
363 tokio::time::sleep(lameduck).await;
364 drop(probe);
365 server.shutdown().await?;
366 }
367 Ok(())
368 }
369 });
370 }
371 #[cfg(feature = "plugin-ready")]
372 for address in ready_addresses {
373 let listener = Arc::new(std::sync::Mutex::new(None::<pharos_server::http::Listener>));
374 let startup_listener = Arc::clone(&listener);
375 let state = Arc::clone(&ready_state);
376 let start = move || {
377 let address = address.clone();
378 let state = Arc::clone(&state);
379 let listener = Arc::clone(&startup_listener);
380 async move {
381 if listener.lock().unwrap().is_some() {
382 return Ok(());
383 }
384 let server =
385 pharos_server::http::Listener::bind_with_status(&address, move |path| {
386 (path == "/ready").then(|| {
387 let (status, body) = state.response();
388 (status, "text/plain; charset=utf-8", body)
389 })
390 })
391 .await?;
392 *listener.lock().unwrap() = Some(server);
393 Ok(())
394 }
395 };
396 lifecycle.register(pharos_server::Phase::Startup, start.clone());
397 lifecycle.register(pharos_server::Phase::RestartFailed, start);
398 let stop = move || {
399 let server = listener.lock().unwrap().take();
400 async move {
401 if let Some(server) = server {
402 server.shutdown().await?;
403 }
404 Ok(())
405 }
406 };
407 lifecycle.register(pharos_server::Phase::Restart, stop.clone());
408 lifecycle.register(pharos_server::Phase::Shutdown, stop);
409 }
410 #[cfg(feature = "plugin-prometheus")]
411 for (address, tls_config) in exporters {
412 let listener = Arc::new(std::sync::Mutex::new(None::<pharos_server::http::Listener>));
413 let startup_listener = Arc::clone(&listener);
414 let registry = metrics.clone();
415 let start = move || {
416 let address = address.clone();
417 let tls_config = tls_config.clone();
418 let registry = registry.clone();
419 let listener = Arc::clone(&startup_listener);
420 async move {
421 if listener.lock().unwrap().is_some() {
422 return Ok(());
423 }
424 if tls_config.is_some() {
425 return Err(io::Error::new(
427 io::ErrorKind::Unsupported,
428 "prometheus TLS configuration is not supported",
429 ));
430 }
431 let server = pharos_server::http::Listener::bind(&address, move |path| {
432 if path != "/metrics" {
433 return None;
434 }
435 let mut body = String::new();
436 registry.encode(&mut body);
437 Some(("text/plain; version=0.0.4; charset=utf-8", body))
438 })
439 .await?;
440 *listener.lock().unwrap() = Some(server);
441 Ok(())
442 }
443 };
444 lifecycle.register(pharos_server::Phase::Startup, start.clone());
445 lifecycle.register(pharos_server::Phase::RestartFailed, start);
446 let stop = move || {
447 let server = listener.lock().unwrap().take();
448 async move {
449 if let Some(server) = server {
450 server.shutdown().await?;
451 }
452 Ok(())
453 }
454 };
455 lifecycle.register(pharos_server::Phase::Restart, stop.clone());
456 lifecycle.register(pharos_server::Phase::Shutdown, stop);
457 }
458 Ok((servers, lifecycle))
459}
460
461#[derive(Debug)]
463pub struct Unimplemented;
464
465impl pharos_server::Plugin for Unimplemented {
466 async fn serve<N: pharos_server::Next + Send>(
467 &self,
468 _cx: &mut pharos_server::Context<'_>,
469 _next: N,
470 ) -> pharos_server::Outcome {
471 pharos_server::Outcome::Error(pharos_server::Error::NotImplemented)
472 }
473}
474
475#[cfg(feature = "plugin-any")]
476fn setup_any(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
477 cursor.next();
478 if cursor.next_arg() {
479 return Err(cursor.arg_err());
480 }
481 block.config.any = Some(any::Any);
482 Ok(())
483}
484
485#[cfg(feature = "plugin-chaos")]
486fn setup_chaos(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
487 block.config.chaos = Some(chaos::Chaos::parse(cursor)?);
488 Ok(())
489}
490
491#[cfg(feature = "plugin-local")]
492fn setup_local(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
493 block.config.local = Some(local::Local::parse(cursor)?);
494 Ok(())
495}
496
497#[cfg(feature = "plugin-whoami")]
498fn setup_whoami(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
499 cursor.next();
500 if cursor.next_arg() {
501 return Err(cursor.arg_err());
502 }
503 block.config.whoami = Some(whoami::Whoami);
504 Ok(())
505}
506
507#[cfg(feature = "plugin-minimal")]
508fn setup_minimal(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
509 cursor.next();
510 if cursor.next_arg() {
511 return Err(cursor.arg_err());
512 }
513 block.config.minimal = Some(minimal::Minimal);
514 Ok(())
515}
516
517#[cfg(feature = "plugin-nsid")]
518fn setup_nsid(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
519 block.config.nsid = Some(nsid::Nsid::parse(cursor)?);
520 Ok(())
521}
522
523#[cfg(feature = "plugin-bufsize")]
524fn setup_bufsize(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
525 block.config.bufsize = Some(bufsize::Bufsize::parse(cursor)?);
526 Ok(())
527}
528
529#[cfg(feature = "plugin-header")]
530fn setup_header(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
531 block.config.header = Some(header::Header::parse(cursor)?);
532 Ok(())
533}
534
535#[cfg(feature = "plugin-loop")]
536fn setup_loop(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
537 cursor.next();
538 if cursor.next_arg() {
539 return Err(cursor.arg_err());
540 }
541 let domain = block.keys().first().map_or(".", |key| key.domain.as_str());
542 block.config.r#loop = Some(r#loop::Loop::new(domain));
543 Ok(())
544}
545
546#[cfg(feature = "plugin-cache")]
547fn setup_cache(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
548 let zones = block
549 .keys()
550 .iter()
551 .map(|key| key.domain.clone())
552 .collect::<Vec<_>>();
553 block.config.cache = Some(cache::Cache::parse(cursor, &zones)?);
554 Ok(())
555}
556
557#[cfg(feature = "plugin-log")]
558fn setup_log(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
559 block.config.log = Some(log::parse(cursor)?);
560 Ok(())
561}
562
563#[cfg(feature = "plugin-metadata")]
564fn setup_metadata(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
565 let domains = block
566 .keys()
567 .iter()
568 .map(|key| key.domain.as_str())
569 .collect::<Vec<_>>();
570 block.config.metadata = Some(metadata::parse(cursor, &domains)?);
571 Ok(())
572}
573
574#[cfg(feature = "plugin-prometheus")]
575fn setup_prometheus(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
576 block.config.prometheus = Some(prometheus::parse(cursor)?);
577 Ok(())
578}
579
580#[cfg(feature = "plugin-ready")]
581fn setup_ready(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
582 block.config.ready = Some(ready::parse(cursor)?);
583 Ok(())
584}
585
586#[cfg(feature = "plugin-reload")]
587fn setup_reload(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
588 block.config.reload = Some(reload::parse(cursor)?);
589 Ok(())
590}
591
592#[cfg(feature = "plugin-bind")]
593fn setup_bind(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
594 block.listen_addresses = bind::parse(cursor)?;
595 block.config.bind = Some(());
596 Ok(())
597}
598
599#[cfg(feature = "plugin-multisocket")]
600fn setup_multisocket(
601 cursor: &mut Cursor<'_>,
602 block: &mut BlockContext<Chain>,
603) -> Result<(), Error> {
604 cursor.next();
605 let args = cursor.remaining_args();
606 if args.len() > 1 || cursor.next() {
607 return Err(cursor.arg_err());
608 }
609 let count = args
610 .first()
611 .map(|arg| {
612 arg.parse::<usize>()
613 .ok()
614 .filter(|count| (1..=1024).contains(count))
615 .ok_or_else(|| cursor.error("invalid socket count"))
616 })
617 .transpose()?;
618 block.config.multisocket = Some(count);
619 Ok(())
620}
621
622#[cfg(feature = "plugin-forward")]
623fn setup_forward(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
624 block.config.forward = Some(forward::Forward::parse(cursor)?);
625 Ok(())
626}
627
628#[cfg(feature = "plugin-health")]
629fn setup_health(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
630 block.config.health = Some(health::parse(cursor)?);
631 Ok(())
632}
633
634#[cfg(feature = "plugin-hosts")]
635fn setup_hosts(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
636 let domains = block
637 .keys()
638 .iter()
639 .map(|key| key.domain.as_str())
640 .collect::<Vec<_>>();
641 let hosts = hosts::Hosts::parse(cursor, &domains)?;
642 hosts.register_hooks(&mut block.config.lifecycle);
643 block.config.hosts = Some(hosts);
644 Ok(())
645}
646
647#[cfg(feature = "plugin-loadbalance")]
648fn setup_loadbalance(
649 cursor: &mut Cursor<'_>,
650 block: &mut BlockContext<Chain>,
651) -> Result<(), Error> {
652 let plugin = loadbalance::LoadBalance::parse(cursor)?;
653 plugin.register_hooks(&mut block.config.lifecycle);
654 block.config.loadbalance = Some(plugin);
655 Ok(())
656}
657
658#[cfg(feature = "plugin-errors")]
659fn setup_errors(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
660 let plugin = errors::parse(cursor)?;
661 plugin.register_shutdown(&mut block.config.lifecycle);
662 block.config.errors = Some(plugin);
663 Ok(())
664}
665
666#[allow(dead_code)]
667fn setup_unimplemented(cursor: &mut Cursor<'_>, _: &mut BlockContext<Chain>) -> Result<(), Error> {
668 cursor.next();
669 Err(cursor.error(format!("{}: plugin is not implemented", cursor.val())))
670}
671
672#[cfg(feature = "plugin-kubernetes")]
673fn setup_kubernetes(cursor: &mut Cursor<'_>, block: &mut BlockContext<Chain>) -> Result<(), Error> {
674 let domains = block
675 .keys()
676 .iter()
677 .map(|key| key.domain.as_str())
678 .collect::<Vec<_>>();
679 let config = kubernetes::setup::parse(cursor, &domains)?;
680 let plugin = kubernetes::Kubernetes::with_listen_addresses(config, &block.listen_addresses)
681 .map_err(|error| cursor.error(error))?;
682 plugin.register_hooks(&mut block.config.lifecycle);
683 block.config.kubernetes = Some(plugin);
684 Ok(())
685}