unified dns api error and move zone operations to management api
This commit is contained in:
parent
247f72871e
commit
96f66e1fd7
5 changed files with 94 additions and 80 deletions
|
@ -16,9 +16,12 @@ pub trait RecordApi {
|
||||||
|
|
||||||
// Zone management api, todo
|
// Zone management api, todo
|
||||||
// E.g.: Manage catalog zone, dynamically generate knot / bind / nsd config...
|
// E.g.: Manage catalog zone, dynamically generate knot / bind / nsd config...
|
||||||
|
#[async_trait]
|
||||||
pub trait ZoneApi {
|
pub trait ZoneApi {
|
||||||
|
type Error;
|
||||||
// get_zones
|
// get_zones
|
||||||
// add_zone
|
// add_zone
|
||||||
// delete_zone
|
// delete_zone
|
||||||
// exists
|
// zone_exists
|
||||||
|
async fn zone_exists(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
|
@ -6,11 +6,11 @@ use trust_dns_client::proto::xfer::{DnsRequestOptions};
|
||||||
|
|
||||||
use super::{Name, Record, RData};
|
use super::{Name, Record, RData};
|
||||||
use super::client::{ClientResponse, DnsClient};
|
use super::client::{ClientResponse, DnsClient};
|
||||||
use super::api::RecordApi;
|
use super::api::{RecordApi, ZoneApi};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MessageError {
|
pub enum DnsApiError {
|
||||||
RecordNotInZone {
|
RecordNotInZone {
|
||||||
zone: Name,
|
zone: Name,
|
||||||
class: DNSClass,
|
class: DNSClass,
|
||||||
|
@ -18,7 +18,10 @@ pub enum MessageError {
|
||||||
mismatched_zone: Vec<Record>,
|
mismatched_zone: Vec<Record>,
|
||||||
},
|
},
|
||||||
ClientError(ClientError),
|
ClientError(ClientError),
|
||||||
ResponceNotOk(ResponseCode)
|
ResponceNotOk {
|
||||||
|
code: ResponseCode,
|
||||||
|
zone: Name,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DnsApiClient {
|
pub struct DnsApiClient {
|
||||||
|
@ -36,18 +39,21 @@ impl DnsApiClient {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl RecordApi for DnsApiClient {
|
impl RecordApi for DnsApiClient {
|
||||||
type Error = MessageError;
|
type Error = DnsApiError;
|
||||||
|
|
||||||
async fn get_records(&mut self, zone: Name, class: DNSClass) -> Result<Vec<Record>, Self::Error>
|
async fn get_records(&mut self, zone: Name, class: DNSClass) -> Result<Vec<Record>, Self::Error>
|
||||||
{
|
{
|
||||||
let response = {
|
let response = {
|
||||||
let mut query = Query::query(zone, RecordType::AXFR);
|
let mut query = Query::query(zone.clone(), RecordType::AXFR);
|
||||||
query.set_query_class(class);
|
query.set_query_class(class);
|
||||||
ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| MessageError::ClientError(e))?
|
ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))?
|
||||||
};
|
};
|
||||||
|
|
||||||
if response.response_code() != ResponseCode::NoError {
|
if response.response_code() != ResponseCode::NoError {
|
||||||
return Err(MessageError::ResponceNotOk(response.response_code()));
|
return Err(DnsApiError::ResponceNotOk {
|
||||||
|
code: response.response_code(),
|
||||||
|
zone: zone,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let answers = response.answers();
|
let answers = response.answers();
|
||||||
|
@ -75,7 +81,7 @@ impl RecordApi for DnsApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
if mismatched_class.len() > 0 || mismatched_zone.len() > 0 {
|
if mismatched_class.len() > 0 || mismatched_zone.len() > 0 {
|
||||||
return Err(MessageError::RecordNotInZone {
|
return Err(DnsApiError::RecordNotInZone {
|
||||||
zone,
|
zone,
|
||||||
class,
|
class,
|
||||||
mismatched_zone,
|
mismatched_zone,
|
||||||
|
@ -104,12 +110,40 @@ impl RecordApi for DnsApiClient {
|
||||||
edns.set_version(0);
|
edns.set_version(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = ClientResponse(self.client.send(message)).await.map_err(|e| MessageError::ClientError(e))?;
|
let response = ClientResponse(self.client.send(message)).await.map_err(|e| DnsApiError::ClientError(e))?;
|
||||||
|
|
||||||
if response.response_code() != ResponseCode::NoError {
|
if response.response_code() != ResponseCode::NoError {
|
||||||
return Err(MessageError::ResponceNotOk(response.response_code()));
|
return Err(DnsApiError::ResponceNotOk {
|
||||||
|
code: response.response_code(),
|
||||||
|
zone: zone,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ZoneApi for DnsApiClient {
|
||||||
|
type Error = DnsApiError;
|
||||||
|
|
||||||
|
async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> Result<(), Self::Error>
|
||||||
|
{
|
||||||
|
let response = {
|
||||||
|
let mut query = Query::query(zone.clone(), RecordType::SOA);
|
||||||
|
query.set_query_class(class);
|
||||||
|
ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if response.response_code() != ResponseCode::NoError {
|
||||||
|
return Err(DnsApiError::ResponceNotOk {
|
||||||
|
code: response.response_code(),
|
||||||
|
zone: zone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,3 +13,4 @@ pub use trust_dns_proto::rr::Name;
|
||||||
|
|
||||||
// Reexport module types
|
// Reexport module types
|
||||||
pub use api::{RecordApi, ZoneApi};
|
pub use api::{RecordApi, ZoneApi};
|
||||||
|
pub use dns_api::DnsApiClient;
|
|
@ -6,7 +6,8 @@ use rocket_contrib::json::Json;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use djangohashers::{HasherError};
|
use djangohashers::{HasherError};
|
||||||
use diesel::result::Error as DieselError;
|
use diesel::result::Error as DieselError;
|
||||||
|
use crate::dns::dns_api::DnsApiError;
|
||||||
|
use crate::models;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UserError {
|
pub enum UserError {
|
||||||
|
@ -98,6 +99,37 @@ impl From<UserError> for ErrorResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DnsApiError> for ErrorResponse {
|
||||||
|
fn from(e: DnsApiError) -> Self {
|
||||||
|
match e {
|
||||||
|
DnsApiError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone} => {
|
||||||
|
ErrorResponse::new(
|
||||||
|
Status::BadRequest,
|
||||||
|
"Record list contains records that do not belong to the zone".into()
|
||||||
|
).with_details(
|
||||||
|
json!({
|
||||||
|
"zone_name": zone.to_utf8(),
|
||||||
|
"class": models::DNSClass::from(class),
|
||||||
|
"mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
|
||||||
|
"mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
DnsApiError::ResponceNotOk { code, zone } => {
|
||||||
|
println!("Query for zone {} failed with code {}", zone, code);
|
||||||
|
|
||||||
|
ErrorResponse::new(
|
||||||
|
Status::NotFound,
|
||||||
|
"Zone could not be found".into()
|
||||||
|
).with_details(json!({
|
||||||
|
"zone_name": zone.to_utf8()
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
DnsApiError::ClientError(e) => make_500(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> {
|
impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> {
|
||||||
fn from(e: ErrorResponse) -> Self {
|
fn from(e: ErrorResponse) -> Self {
|
||||||
|
|
|
@ -5,16 +5,10 @@ use rocket::http::Status;
|
||||||
|
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use trust_dns_client::client::ClientHandle;
|
|
||||||
use trust_dns_client::op::ResponseCode;
|
|
||||||
use trust_dns_client::rr::{DNSClass, RecordType};
|
|
||||||
|
|
||||||
use crate::DbConn;
|
use crate::DbConn;
|
||||||
use crate::dns;
|
|
||||||
use crate::dns::api::RecordApi;
|
|
||||||
use crate::dns::dns_api::DnsApiClient;
|
|
||||||
use crate::dns::dns_api::MessageError;
|
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
use crate::dns;
|
||||||
|
use crate::dns::{RecordApi, ZoneApi};
|
||||||
|
|
||||||
|
|
||||||
#[get("/zones/<zone>/records")]
|
#[get("/zones/<zone>/records")]
|
||||||
|
@ -36,22 +30,10 @@ pub async fn get_zone_records(
|
||||||
}
|
}
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
let mut dns_api = DnsApiClient::new(client);
|
let mut dns_api = dns::DnsApiClient::new(client);
|
||||||
|
|
||||||
let records: Vec<_> = match dns_api.get_records(zone.clone(), DNSClass::IN).await {
|
let dns_records = dns_api.get_records(zone.clone(), dns::DNSClass::IN).await?;
|
||||||
Ok(records) => records.into_iter().map(models::Record::from).collect(),
|
let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect();
|
||||||
|
|
||||||
Err(MessageError::ResponceNotOk(code)) => {
|
|
||||||
println!("Querrying AXFR of zone {} failed with code {}", *zone, code);
|
|
||||||
return models::ErrorResponse::new(
|
|
||||||
Status::NotFound,
|
|
||||||
"Zone could not be found".into()
|
|
||||||
).with_details(json!({
|
|
||||||
"zone_name": zone.to_utf8()
|
|
||||||
})).err();
|
|
||||||
},
|
|
||||||
Err(err) => { return models::make_500(err).err(); },
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Json(records))
|
Ok(Json(records))
|
||||||
}
|
}
|
||||||
|
@ -101,37 +83,10 @@ pub async fn create_zone_records(
|
||||||
).err();
|
).err();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dns_api = DnsApiClient::new(client);
|
let mut dns_api = dns::DnsApiClient::new(client);
|
||||||
|
dns_api.add_records(zone.clone(), dns::DNSClass::IN, records).await?;
|
||||||
|
|
||||||
match dns_api.add_records(zone.clone(), DNSClass::IN, records).await {
|
|
||||||
Ok(_) => {
|
|
||||||
return Ok(Json(()));
|
return Ok(Json(()));
|
||||||
//query.await.map_err(models::make_500)?;
|
|
||||||
}
|
|
||||||
Err(MessageError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone}) => {
|
|
||||||
return models::ErrorResponse::new(
|
|
||||||
Status::BadRequest,
|
|
||||||
"Record list contains records that do not belong to the zone".into()
|
|
||||||
).with_details(
|
|
||||||
json!({
|
|
||||||
"zone_name": zone.to_utf8(),
|
|
||||||
"class": models::DNSClass::from(class),
|
|
||||||
"mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
|
|
||||||
"mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::<Vec<models::Record>>(),
|
|
||||||
})
|
|
||||||
).err();
|
|
||||||
},
|
|
||||||
Err(MessageError::ResponceNotOk(code)) => {
|
|
||||||
println!("Update of zone {} failed with code {}", *zone, code);
|
|
||||||
return models::ErrorResponse::new(
|
|
||||||
Status::NotFound,
|
|
||||||
"Update of zone failed".into()
|
|
||||||
).with_details(json!({
|
|
||||||
"zone_name": zone.to_utf8()
|
|
||||||
})).err();
|
|
||||||
},
|
|
||||||
Err(e) => return models::make_500(e).err()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/zones")]
|
#[get("/zones")]
|
||||||
|
@ -155,25 +110,14 @@ pub async fn get_zones(
|
||||||
#[post("/zones", data = "<zone_request>")]
|
#[post("/zones", data = "<zone_request>")]
|
||||||
pub async fn create_zone(
|
pub async fn create_zone(
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
mut client: dns::client::DnsClient,
|
client: dns::client::DnsClient,
|
||||||
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
||||||
zone_request: Json<models::CreateZoneRequest>,
|
zone_request: Json<models::CreateZoneRequest>,
|
||||||
) -> Result<Json<models::Zone>, models::ErrorResponse> {
|
) -> Result<Json<models::Zone>, models::ErrorResponse> {
|
||||||
user_info?.check_admin()?;
|
user_info?.check_admin()?;
|
||||||
|
|
||||||
// Check if the zone exists in the DNS server
|
let mut dns_api = dns::DnsApiClient::new(client);
|
||||||
let response = {
|
dns_api.zone_exists(zone_request.name.clone(), dns::DNSClass::IN).await?;
|
||||||
let query = client.query(zone_request.name.clone(), DNSClass::IN, RecordType::SOA);
|
|
||||||
query.await.map_err(models::make_500)?
|
|
||||||
};
|
|
||||||
|
|
||||||
if response.response_code() != ResponseCode::NoError {
|
|
||||||
println!("Querrying SOA of zone {} failed with code {}", *zone_request.name, response.response_code());
|
|
||||||
return models::ErrorResponse::new(
|
|
||||||
Status::NotFound,
|
|
||||||
format!("Zone {} could not be found", *zone_request.name)
|
|
||||||
).err()
|
|
||||||
}
|
|
||||||
|
|
||||||
let zone = conn.run(move |c| {
|
let zone = conn.run(move |c| {
|
||||||
models::Zone::create_zone(c, zone_request.into_inner())
|
models::Zone::create_zone(c, zone_request.into_inner())
|
||||||
|
|
Loading…
Reference in a new issue