externaldns_webhook/
webhook.rs1use crate::{
2 IDoNotCareWhich, MEDIATYPE, changes::Changes, domain_filter::DomainFilter, endpoint::Endpoint,
3 provider::Provider, status::Status, webhook_json::WebhookJson,
4};
5use actix_web::{
6 App, HttpServer, get,
7 guard::GuardContext,
8 http::{StatusCode, header::Accept},
9 post,
10 web::{Data, Json},
11};
12use logcall::logcall;
13use std::sync::Arc;
14
15#[derive(Debug)]
19pub struct Webhook {
20 provider_address: String,
21 provider_port: u16,
22 dns_manager: Arc<dyn Provider>,
23
24 exposed_address: String,
25 exposed_port: u16,
26 status: Arc<dyn Status>,
27}
28impl Webhook {
29 #[logcall("debug")]
31 pub fn new(dns_manager: Arc<dyn Provider>, status: Arc<dyn Status>) -> Webhook {
32 Webhook {
34 provider_address: "127.0.0.1".to_string(),
35 provider_port: 8888,
36 dns_manager,
37 exposed_address: "0.0.0.0".to_string(),
38 exposed_port: 8080,
39 status,
40 }
41 }
42
43 #[logcall(ok = "debug", err = "error")]
45 pub async fn start(&self) -> anyhow::Result<()> {
46 let x = self.status.clone();
47 let exposed = HttpServer::new(move || {
48 App::new()
49 .app_data(Data::new(x.clone()))
50 .service(get_healthz)
51 .service(get_metrics)
52 })
53 .bind((self.exposed_address.clone(), self.exposed_port))?
54 .run();
55
56 let x = self.dns_manager.clone();
57 let provider = HttpServer::new(move || {
58 App::new()
59 .app_data(Data::new(x.clone()))
60 .service(get_root)
61 .service(get_records)
62 .service(post_records)
63 .service(post_adjustendpoints)
64 })
65 .bind((self.provider_address.clone(), self.provider_port))?
66 .run();
67
68 tokio::spawn(exposed);
69 provider.await?;
70
71 Ok(())
72 }
73}
74
75#[logcall("debug")]
77#[get("/", guard = "media_type_guard")]
78async fn get_root(dns_manager: Data<Arc<dyn Provider>>) -> WebhookJson<DomainFilter> {
79 WebhookJson(Json(dns_manager.domain_filter().await))
80}
81
82#[logcall("debug")]
84#[get("/records", guard = "media_type_guard")]
85async fn get_records(dns_manager: Data<Arc<dyn Provider>>) -> WebhookJson<Vec<Endpoint>> {
86 WebhookJson(Json(dns_manager.records().await))
87}
88
89#[logcall("debug")]
91#[post("/records")]
92async fn post_records(
93 dns_manager: Data<Arc<dyn Provider>>,
94 changes: Json<Changes>,
95) -> (String, StatusCode) {
96 match dns_manager.apply_changes(changes.0).await {
97 Ok(_) => ("".to_string(), StatusCode::OK),
98 Err(e) => (format!("{e:?}"), StatusCode::INTERNAL_SERVER_ERROR),
99 }
100}
101
102#[logcall("debug")]
104#[post("/adjustendpoints", guard = "media_type_guard")]
105async fn post_adjustendpoints(
106 dns_manager: Data<Arc<dyn Provider>>,
107 endpoints: Json<Vec<Endpoint>>,
108) -> (Json<IDoNotCareWhich<Vec<Endpoint>, String>>, StatusCode) {
109 match dns_manager.adjust_endpoints(endpoints.0).await {
110 Ok(x) => (Json(IDoNotCareWhich::One(x)), StatusCode::OK),
111 Err(e) => (
112 Json(IDoNotCareWhich::Another(format!("{e:?}"))),
113 StatusCode::INTERNAL_SERVER_ERROR,
114 ),
115 }
116}
117
118fn media_type_guard(ctx: &GuardContext<'_>) -> bool {
120 ctx.header::<Accept>()
121 .map_or(false, |h| h.preference() == MEDIATYPE)
122}
123
124#[get("/healthz")]
126async fn get_healthz(status: Data<Arc<dyn Status>>) -> (String, StatusCode) {
127 status.healthz().await
128}
129
130#[get("/metrics")]
132async fn get_metrics(status: Data<Arc<dyn Status>>) -> (String, StatusCode) {
133 status.metrics().await
134}