add records update
This commit is contained in:
parent
77cc634257
commit
3767cc6ea0
12 changed files with 266 additions and 87 deletions
23
api.yml
23
api.yml
|
@ -307,6 +307,17 @@ components:
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Record'
|
$ref: '#/components/schemas/Record'
|
||||||
|
|
||||||
|
UpdateRecordsRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- oldRecords
|
||||||
|
- newRecords
|
||||||
|
properties:
|
||||||
|
oldRecords:
|
||||||
|
$ref: '#/components/schemas/RecordList'
|
||||||
|
newRecords:
|
||||||
|
$ref: '#/components/schemas/RecordList'
|
||||||
|
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
'/users':
|
'/users':
|
||||||
|
@ -400,3 +411,15 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
put:
|
||||||
|
security:
|
||||||
|
- ApiToken: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UpdateRecordsRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
|
||||||
|
|
39
e2e/zones.py
39
e2e/zones.py
|
@ -7,7 +7,8 @@ from nomilo_client.models import (
|
||||||
RecordTypeCNAME,
|
RecordTypeCNAME,
|
||||||
RecordTypeNS,
|
RecordTypeNS,
|
||||||
RecordTypeTXT,
|
RecordTypeTXT,
|
||||||
RecordList
|
RecordList,
|
||||||
|
UpdateRecordsRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -97,3 +98,39 @@ class TestZones(unittest.TestCase):
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
self.assertTrue(found, msg='New record not found in zone records')
|
self.assertTrue(found, msg='New record not found in zone records')
|
||||||
|
|
||||||
|
def test_update_records(self):
|
||||||
|
name = random_name('example.com.')
|
||||||
|
old_record = RecordTypeTXT(
|
||||||
|
_class='IN',
|
||||||
|
ttl=300,
|
||||||
|
name=name,
|
||||||
|
text='old value',
|
||||||
|
type='TXT'
|
||||||
|
)
|
||||||
|
|
||||||
|
new_record = RecordTypeTXT(
|
||||||
|
_class='IN',
|
||||||
|
ttl=300,
|
||||||
|
name=name,
|
||||||
|
text='new value',
|
||||||
|
type='TXT'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.api.zones_zone_records_post(zone='example.com.', record_list=RecordList(value=[old_record]))
|
||||||
|
|
||||||
|
update_records_request = UpdateRecordsRequest(
|
||||||
|
old_records=RecordList([old_record]),
|
||||||
|
new_records=RecordList([new_record]),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.api.zones_zone_records_put(zone='example.com.', update_records_request=update_records_request)
|
||||||
|
|
||||||
|
records = self.api.zones_zone_records_get(zone='example.com.')
|
||||||
|
found = False
|
||||||
|
for record in records.value:
|
||||||
|
if type(record) is RecordTypeTXT and record.name == name:
|
||||||
|
self.assertEqual(record.text, new_record.text, msg='New record does not have the expected value')
|
||||||
|
found = True
|
||||||
|
|
||||||
|
self.assertTrue(found, msg='New record not found in zone records')
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub trait RecordApi {
|
||||||
|
|
||||||
async fn get_records(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<Vec<dns::Record>, Self::Error>;
|
async fn get_records(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<Vec<dns::Record>, Self::Error>;
|
||||||
async fn add_records(&mut self, zone: dns::Name, class: dns::DNSClass, new_records: Vec<dns::Record>) -> Result<(), Self::Error>;
|
async fn add_records(&mut self, zone: dns::Name, class: dns::DNSClass, new_records: Vec<dns::Record>) -> Result<(), Self::Error>;
|
||||||
// update_records
|
async fn update_records(&mut self, zone: dns::Name, class: dns::DNSClass, old_records: Vec<dns::Record>, new_records: Vec<dns::Record>) -> Result<(), Self::Error>;
|
||||||
// delete_records
|
// delete_records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,5 @@ pub trait ZoneApi {
|
||||||
// get_zones
|
// get_zones
|
||||||
// add_zone
|
// add_zone
|
||||||
// delete_zone
|
// delete_zone
|
||||||
// zone_exists
|
|
||||||
async fn zone_exists(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<(), Self::Error>;
|
async fn zone_exists(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
use trust_dns_proto::DnsHandle;
|
use trust_dns_proto::DnsHandle;
|
||||||
|
use trust_dns_client::client::ClientHandle;
|
||||||
use trust_dns_client::rr::{DNSClass, RecordType};
|
use trust_dns_client::rr::{DNSClass, RecordType};
|
||||||
use trust_dns_client::op::{UpdateMessage, OpCode, MessageType, Message, Query, ResponseCode};
|
use trust_dns_client::op::{UpdateMessage, OpCode, MessageType, Message, Query, ResponseCode};
|
||||||
use trust_dns_client::error::ClientError;
|
use trust_dns_client::error::ClientError;
|
||||||
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};
|
||||||
|
@ -11,12 +11,6 @@ use super::api::{RecordApi, ZoneApi};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DnsApiError {
|
pub enum DnsApiError {
|
||||||
RecordNotInZone {
|
|
||||||
zone: Name,
|
|
||||||
class: DNSClass,
|
|
||||||
mismatched_class: Vec<Record>,
|
|
||||||
mismatched_zone: Vec<Record>,
|
|
||||||
},
|
|
||||||
ClientError(ClientError),
|
ClientError(ClientError),
|
||||||
ResponceNotOk {
|
ResponceNotOk {
|
||||||
code: ResponseCode,
|
code: ResponseCode,
|
||||||
|
@ -44,9 +38,8 @@ impl RecordApi for DnsApiClient {
|
||||||
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.clone(), RecordType::AXFR);
|
let query = self.client.query(zone.clone(), class, RecordType::AXFR);
|
||||||
query.set_query_class(class);
|
query.await.map_err(|e| DnsApiError::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 {
|
||||||
|
@ -68,31 +61,14 @@ impl RecordApi for DnsApiClient {
|
||||||
|
|
||||||
async fn add_records(&mut self, zone: Name, class: DNSClass, new_records: Vec<Record>) -> Result<(), Self::Error>
|
async fn add_records(&mut self, zone: Name, class: DNSClass, new_records: Vec<Record>) -> Result<(), Self::Error>
|
||||||
{
|
{
|
||||||
let mut mismatched_class = Vec::new();
|
// Taken from trust_dns_client::op::update_message::append
|
||||||
let mut mismatched_zone = Vec::new();
|
// The original function can not be used as is because it takes a RecordSet and not a Record list
|
||||||
|
|
||||||
for record in new_records.iter() {
|
|
||||||
if !zone.zone_of(record.name()) {
|
|
||||||
mismatched_zone.push(record.clone());
|
|
||||||
}
|
|
||||||
if record.dns_class() != class {
|
|
||||||
mismatched_class.push(record.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mismatched_class.len() > 0 || mismatched_zone.len() > 0 {
|
|
||||||
return Err(DnsApiError::RecordNotInZone {
|
|
||||||
zone,
|
|
||||||
class,
|
|
||||||
mismatched_zone,
|
|
||||||
mismatched_class
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut zone_query = Query::new();
|
let mut zone_query = Query::new();
|
||||||
zone_query.set_name(zone.clone())
|
zone_query.set_name(zone.clone())
|
||||||
.set_query_class(class)
|
.set_query_class(class)
|
||||||
.set_query_type(RecordType::SOA);
|
.set_query_type(RecordType::SOA);
|
||||||
|
|
||||||
let mut message = Message::new();
|
let mut message = Message::new();
|
||||||
|
|
||||||
// TODO: set random / time based id
|
// TODO: set random / time based id
|
||||||
|
@ -121,6 +97,68 @@ impl RecordApi for DnsApiClient {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_records(&mut self, zone: Name, class: DNSClass, old_records: Vec<Record>, new_records: Vec<Record>) -> Result<(), Self::Error>
|
||||||
|
{
|
||||||
|
|
||||||
|
// Taken from trust_dns_client::op::update_message::compare_and_swap
|
||||||
|
// The original function can not be used as is because it takes a RecordSet and not a Record list
|
||||||
|
|
||||||
|
// for updates, the query section is used for the zone
|
||||||
|
let mut zone_query: Query = Query::new();
|
||||||
|
zone_query.set_name(zone.clone())
|
||||||
|
.set_query_class(class)
|
||||||
|
.set_query_type(RecordType::SOA);
|
||||||
|
|
||||||
|
let mut message: Message = Message::new();
|
||||||
|
|
||||||
|
// build the message
|
||||||
|
// TODO: set random / time based id
|
||||||
|
message
|
||||||
|
.set_id(0)
|
||||||
|
.set_message_type(MessageType::Query)
|
||||||
|
.set_op_code(OpCode::Update)
|
||||||
|
.set_recursion_desired(false);
|
||||||
|
message.add_zone(zone_query);
|
||||||
|
|
||||||
|
// make sure the record is what is expected
|
||||||
|
let mut prerequisite = old_records.clone();
|
||||||
|
for record in prerequisite.iter_mut() {
|
||||||
|
record.set_ttl(0);
|
||||||
|
}
|
||||||
|
message.add_pre_requisites(prerequisite);
|
||||||
|
|
||||||
|
// add the delete for the old record
|
||||||
|
let mut delete = old_records;
|
||||||
|
// the class must be none for delete
|
||||||
|
for record in delete.iter_mut() {
|
||||||
|
record.set_dns_class(DNSClass::NONE);
|
||||||
|
// the TTL should be 0
|
||||||
|
record.set_ttl(0);
|
||||||
|
}
|
||||||
|
message.add_updates(delete);
|
||||||
|
|
||||||
|
// insert the new record...
|
||||||
|
message.add_updates(new_records);
|
||||||
|
|
||||||
|
// Extended dns
|
||||||
|
{
|
||||||
|
let edns = message.edns_mut();
|
||||||
|
edns.set_max_payload(1232);
|
||||||
|
edns.set_version(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = ClientResponse(self.client.send(message)).await.map_err(|e| DnsApiError::ClientError(e))?;
|
||||||
|
|
||||||
|
if response.response_code() != ResponseCode::NoError {
|
||||||
|
return Err(DnsApiError::ResponceNotOk {
|
||||||
|
code: response.response_code(),
|
||||||
|
zone: zone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,9 +169,8 @@ impl ZoneApi for DnsApiClient {
|
||||||
async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> Result<(), Self::Error>
|
async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> Result<(), Self::Error>
|
||||||
{
|
{
|
||||||
let response = {
|
let response = {
|
||||||
let mut query = Query::query(zone.clone(), RecordType::SOA);
|
let query = self.client.query(zone.clone(), class, RecordType::SOA);
|
||||||
query.set_query_class(class);
|
query.await.map_err(|e| DnsApiError::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 {
|
||||||
|
|
|
@ -14,3 +14,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;
|
pub use dns_api::DnsApiClient;
|
||||||
|
pub use client::DnsClient;
|
|
@ -28,6 +28,7 @@ async fn rocket() -> rocket::Rocket {
|
||||||
.mount("/api/v1", routes![
|
.mount("/api/v1", routes![
|
||||||
get_zone_records,
|
get_zone_records,
|
||||||
create_zone_records,
|
create_zone_records,
|
||||||
|
update_zone_records,
|
||||||
get_zones,
|
get_zones,
|
||||||
create_zone,
|
create_zone,
|
||||||
add_member_to_zone,
|
add_member_to_zone,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::dns;
|
use crate::dns;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub enum DNSClass {
|
pub enum DNSClass {
|
||||||
IN,
|
IN,
|
||||||
CH,
|
CH,
|
||||||
|
|
|
@ -102,19 +102,6 @@ impl From<UserError> for ErrorResponse {
|
||||||
impl From<DnsApiError> for ErrorResponse {
|
impl From<DnsApiError> for ErrorResponse {
|
||||||
fn from(e: DnsApiError) -> Self {
|
fn from(e: DnsApiError) -> Self {
|
||||||
match e {
|
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 } => {
|
DnsApiError::ResponceNotOk { code, zone } => {
|
||||||
println!("Query for zone {} failed with code {}", zone, code);
|
println!("Query for zone {} failed with code {}", zone, code);
|
||||||
|
|
||||||
|
@ -132,17 +119,34 @@ impl From<DnsApiError> for ErrorResponse {
|
||||||
|
|
||||||
impl From<models::RecordListParseError> for ErrorResponse {
|
impl From<models::RecordListParseError> for ErrorResponse {
|
||||||
fn from(e: models::RecordListParseError) -> Self {
|
fn from(e: models::RecordListParseError) -> Self {
|
||||||
models::ErrorResponse::new(
|
match e {
|
||||||
|
models::RecordListParseError::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,
|
||||||
|
"mismatched_zone": mismatched_zone,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
models::RecordListParseError::ParseError { zone, bad_records } => {
|
||||||
|
ErrorResponse::new(
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
"Record list contains records that could not be parsed into DNS records".into()
|
"Record list contains records that could not be parsed into DNS records".into()
|
||||||
).with_details(
|
).with_details(
|
||||||
json!({
|
json!({
|
||||||
"zone_name": e.zone.to_utf8(),
|
"zone_name": zone.to_utf8(),
|
||||||
"records": e.bad_records
|
"records": bad_records
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> {
|
impl<S> From<ErrorResponse> for Outcome<S, ErrorResponse> {
|
||||||
|
|
|
@ -15,5 +15,5 @@ pub use errors::{UserError, ErrorResponse, make_500};
|
||||||
pub use name::{AbsoluteName, SerdeName};
|
pub use name::{AbsoluteName, SerdeName};
|
||||||
pub use user::{LocalUser, UserInfo, Role, UserZone, User, CreateUserRequest};
|
pub use user::{LocalUser, UserInfo, Role, UserZone, User, CreateUserRequest};
|
||||||
pub use rdata::RData;
|
pub use rdata::RData;
|
||||||
pub use record::{Record, RecordList, ParseRecordList, RecordListParseError};
|
pub use record::{Record, RecordList, ParseRecordList, RecordListParseError, UpdateRecordsRequest};
|
||||||
pub use zone::{Zone, AddZoneMemberRequest, CreateZoneRequest};
|
pub use zone::{Zone, AddZoneMemberRequest, CreateZoneRequest};
|
|
@ -11,7 +11,7 @@ use crate::dns;
|
||||||
use super::name::SerdeName;
|
use super::name::SerdeName;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
#[serde(tag = "Type")]
|
#[serde(tag = "Type")]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub enum RData {
|
pub enum RData {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::class::DNSClass;
|
||||||
use super::rdata::RData;
|
use super::rdata::RData;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
#[serde(rename = "Name")]
|
#[serde(rename = "Name")]
|
||||||
pub name: SerdeName,
|
pub name: SerdeName,
|
||||||
|
@ -45,36 +45,78 @@ impl TryFrom<Record> for dns::Record {
|
||||||
|
|
||||||
pub type RecordList = Vec<Record>;
|
pub type RecordList = Vec<Record>;
|
||||||
|
|
||||||
pub struct RecordListParseError {
|
#[derive(Debug, Deserialize)]
|
||||||
pub bad_records: Vec<Record>,
|
#[serde(rename_all = "camelCase")]
|
||||||
pub zone: dns::Name,
|
pub struct UpdateRecordsRequest {
|
||||||
|
pub old_records: RecordList,
|
||||||
|
pub new_records: RecordList,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RecordListParseError {
|
||||||
|
ParseError {
|
||||||
|
bad_records: Vec<Record>,
|
||||||
|
zone: dns::Name,
|
||||||
|
},
|
||||||
|
RecordNotInZone {
|
||||||
|
zone: dns::Name,
|
||||||
|
class: dns::DNSClass,
|
||||||
|
mismatched_class: Vec<Record>,
|
||||||
|
mismatched_zone: Vec<Record>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ParseRecordList {
|
pub trait ParseRecordList {
|
||||||
fn try_into_dns_type(self, zone: dns::Name) -> Result<Vec<dns::Record>, RecordListParseError>;
|
fn try_into_dns_type(self, zone: dns::Name, class: dns::DNSClass) -> Result<Vec<dns::Record>, RecordListParseError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseRecordList for RecordList {
|
impl ParseRecordList for RecordList {
|
||||||
fn try_into_dns_type(self, zone: dns::Name) -> Result<Vec<dns::Record>, RecordListParseError> {
|
fn try_into_dns_type(self, zone: dns::Name, class: dns::DNSClass) -> Result<Vec<dns::Record>, RecordListParseError> {
|
||||||
// TODO: What about relative names (also in cnames and stuff)
|
// TODO: What about relative names (also in cnames and stuff)
|
||||||
let mut bad_records = Vec::new();
|
let mut bad_records = Vec::new();
|
||||||
let mut records: Vec<dns::Record> = Vec::new();
|
let mut records: Vec<dns::Record> = Vec::new();
|
||||||
|
let mut mismatched_class: Vec<Record> = Vec::new();
|
||||||
|
let mut mismatched_zone: Vec<Record> = Vec::new();
|
||||||
|
|
||||||
for record in self.into_iter() {
|
for record in self.into_iter() {
|
||||||
let this_record = record.clone();
|
let this_record = record.clone();
|
||||||
if let Ok(record) = record.try_into() {
|
if let Ok(record) = dns::Record::try_from(record) {
|
||||||
|
let mut good_record = true;
|
||||||
|
|
||||||
|
if !zone.zone_of(record.name()) {
|
||||||
|
mismatched_zone.push(this_record.clone());
|
||||||
|
good_record = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.dns_class() != class {
|
||||||
|
mismatched_class.push(this_record.clone());
|
||||||
|
good_record = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if good_record {
|
||||||
records.push(record);
|
records.push(record);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bad_records.push(this_record.clone());
|
bad_records.push(this_record.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bad_records.is_empty() {
|
if !bad_records.is_empty() {
|
||||||
return Err(RecordListParseError {
|
return Err(RecordListParseError::ParseError {
|
||||||
zone,
|
zone,
|
||||||
bad_records,
|
bad_records,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !mismatched_class.is_empty() || !mismatched_zone.is_empty() {
|
||||||
|
return Err(RecordListParseError::RecordNotInZone {
|
||||||
|
zone,
|
||||||
|
class,
|
||||||
|
mismatched_zone,
|
||||||
|
mismatched_class
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(records)
|
return Ok(records)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,18 @@ use rocket::http::Status;
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use crate::DbConn;
|
use crate::DbConn;
|
||||||
use crate::dns;
|
use crate::dns::{DnsClient, DnsApiClient, RecordApi, ZoneApi};
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::dns::{RecordApi, ZoneApi};
|
|
||||||
use crate::models::{ParseRecordList};
|
use crate::models::{ParseRecordList};
|
||||||
|
|
||||||
|
|
||||||
#[get("/zones/<zone>/records")]
|
#[get("/zones/<zone>/records")]
|
||||||
pub async fn get_zone_records(
|
pub async fn get_zone_records(
|
||||||
client: dns::client::DnsClient,
|
client: DnsClient,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
||||||
zone: models::AbsoluteName
|
zone: models::AbsoluteName
|
||||||
) -> Result<Json<Vec<models::Record>>, models::ErrorResponse> {
|
) -> Result<Json<models::RecordList>, models::ErrorResponse> {
|
||||||
|
|
||||||
let user_info = user_info?;
|
let user_info = user_info?;
|
||||||
let zone_name = zone.to_string();
|
let zone_name = zone.to_string();
|
||||||
|
@ -29,9 +28,9 @@ pub async fn get_zone_records(
|
||||||
}
|
}
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
let mut dns_api = dns::DnsApiClient::new(client);
|
let mut dns_api = DnsApiClient::new(client);
|
||||||
|
|
||||||
let dns_records = dns_api.get_records(zone.clone(), dns::DNSClass::IN).await?;
|
let dns_records = dns_api.get_records(zone.clone(), models::DNSClass::IN.into()).await?;
|
||||||
let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect();
|
let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect();
|
||||||
|
|
||||||
Ok(Json(records))
|
Ok(Json(records))
|
||||||
|
@ -39,11 +38,11 @@ pub async fn get_zone_records(
|
||||||
|
|
||||||
#[post("/zones/<zone>/records", data = "<new_records>")]
|
#[post("/zones/<zone>/records", data = "<new_records>")]
|
||||||
pub async fn create_zone_records(
|
pub async fn create_zone_records(
|
||||||
client: dns::client::DnsClient,
|
client: DnsClient,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
||||||
zone: models::AbsoluteName,
|
zone: models::AbsoluteName,
|
||||||
new_records: Json<Vec<models::Record>>
|
new_records: Json<models::RecordList>
|
||||||
) -> Result<Json<()>, models::ErrorResponse> {
|
) -> Result<Json<()>, models::ErrorResponse> {
|
||||||
|
|
||||||
let user_info = user_info?;
|
let user_info = user_info?;
|
||||||
|
@ -58,17 +57,53 @@ pub async fn create_zone_records(
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
|
|
||||||
let mut dns_api = dns::DnsApiClient::new(client);
|
let mut dns_api = DnsApiClient::new(client);
|
||||||
|
|
||||||
dns_api.add_records(
|
dns_api.add_records(
|
||||||
zone.clone(),
|
zone.clone(),
|
||||||
models::DNSClass::IN.into(),
|
models::DNSClass::IN.into(),
|
||||||
new_records.into_inner().try_into_dns_type(zone.into_inner())?
|
new_records.into_inner().try_into_dns_type(zone.into_inner(), models::DNSClass::IN.into())?
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
return Ok(Json(()));
|
return Ok(Json(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[put("/zones/<zone>/records", data = "<update_records_request>")]
|
||||||
|
pub async fn update_zone_records(
|
||||||
|
client: DnsClient,
|
||||||
|
conn: DbConn,
|
||||||
|
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
||||||
|
zone: models::AbsoluteName,
|
||||||
|
update_records_request: Json<models::UpdateRecordsRequest>
|
||||||
|
) -> Result<Json<()>, models::ErrorResponse> {
|
||||||
|
|
||||||
|
let user_info = user_info?;
|
||||||
|
let zone = zone.into_inner();
|
||||||
|
let zone_name = zone.to_utf8();
|
||||||
|
let update_records_request = update_records_request.into_inner();
|
||||||
|
|
||||||
|
conn.run(move |c| {
|
||||||
|
if user_info.is_admin() {
|
||||||
|
models::Zone::get_by_name(c, &zone_name)
|
||||||
|
} else {
|
||||||
|
user_info.get_zone(c, &zone_name)
|
||||||
|
}
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
|
||||||
|
let mut dns_api = DnsApiClient::new(client);
|
||||||
|
|
||||||
|
dns_api.update_records(
|
||||||
|
zone.clone(),
|
||||||
|
models::DNSClass::IN.into(),
|
||||||
|
update_records_request.old_records.try_into_dns_type(zone.clone(), models::DNSClass::IN.into())?,
|
||||||
|
update_records_request.new_records.try_into_dns_type(zone, models::DNSClass::IN.into())?,
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
return Ok(Json(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[get("/zones")]
|
#[get("/zones")]
|
||||||
pub async fn get_zones(
|
pub async fn get_zones(
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
|
@ -90,14 +125,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,
|
||||||
client: dns::client::DnsClient,
|
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()?;
|
||||||
|
|
||||||
let mut dns_api = dns::DnsApiClient::new(client);
|
let mut dns_api = DnsApiClient::new(client);
|
||||||
dns_api.zone_exists(zone_request.name.clone(), dns::DNSClass::IN).await?;
|
dns_api.zone_exists(zone_request.name.clone(), models::DNSClass::IN.into()).await?;
|
||||||
|
|
||||||
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