Skip to main content

pharos_plugins/
lib.rs

1#![forbid(unsafe_code)]
2#![recursion_limit = "512"]
3//! Plugins are enabled by `plugin-<directive>` Cargo features (underscores in
4//! Directive names become hyphens). Enabled but unimplemented Directives reject
5//! configuration rather than silently accepting it.
6
7use 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/// Directive registry in Plugin Order.
98#[must_use]
99pub fn directives() -> &'static [Directive<'static, Chain>] {
100    DIRECTIVES
101}
102
103/// CoreDNS-style alphabetically sorted plugin listing, including final blank line.
104#[must_use]
105pub const fn plugins_listing() -> &'static str {
106    PLUGINS_LISTING
107}
108
109/// Configured DNS Servers keyed by Listener.
110pub type Servers = HashMap<ListenerKey, Server<Arc<Chain>>>;
111
112/// Group Server Blocks by Listener and collect their hooks in block order.
113/// The caller runs Startup before serving and Shutdown before Listeners drain.
114pub fn servers(blocks: Vec<BlockContext<Chain>>) -> io::Result<(Servers, Lifecycle)> {
115    servers_with_registry(blocks, pharos_server::metrics::Registry::default())
116}
117
118/// Build servers using a process-wide metrics registry.
119pub 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        // This hook precedes all block shutdown hooks, including a future lameduck delay.
144        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                    // POSIX.1-2024 if_nametoindex: map an interface name to its index.
226                    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                    // shortcut: exporter-toolkit TLS and authentication require a compatible configuration implementation.
426                    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/// Placeholder for enabled but unimplemented Plugins, whose setup always fails.
462#[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}