Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
- [Flavors](./tui/compute/flavors.md)
- [Compute Aggregates](./tui/compute/aggregates.md)
- [Compute Hypervisors](./tui/compute/hypervisors.md)
- [DNS](./tui/dns/index.md)
- [Zones](./tui/dns/zones.md)
- [Recordsets](./tui/dns/recordsets.md)
- [Identity](./tui/identity/index.md)
- [Application Credentials](./tui/identity/application-credentials.md)
- [Groups](./tui/identity/groups.md)
Expand Down
Empty file added doc/src/tui/dns/index.md
Empty file.
3 changes: 3 additions & 0 deletions doc/src/tui/dns/recordsets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# DNS Recordsets

This view lists all DNS recordsets.
6 changes: 6 additions & 0 deletions doc/src/tui/dns/zones.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# DNS Zones

This view lists all DNS zones.

Pressing `<r>` on the zone will switch to listing recorsets of the selected
zone.
13 changes: 12 additions & 1 deletion openstack_tui/.config/config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
"ComputeHypervisors": {
"y": { "action": "DescribeApiResponse", "description": "YAML"},
},
"DnsRecordsets": {
"y": { "action": "DescribeApiResponse", "description": "YAML"},
"0": { "action": {"SetDnsRecordsetFilters": {} }, "description": "Default filters", "type": "Filter"},
},
"DnsZones": {
"y": { "action": "DescribeApiResponse", "description": "YAML"},
"r": { "action": "ShowDnsZoneRecordsets", "description": "Recordsets"},
"ctrl-d": { "action": "DeleteDnsZone", "description": "Delete"},
},
"IdentityApplicationCredentials": {
"y": { "action": "DescribeApiResponse", "description": "YAML"},
},
Expand Down Expand Up @@ -109,6 +118,7 @@
"nets": "NetworkNetworks",
"networks": "NetworkNetworks",
"projects": "IdentityProjects",
"recordsets (dns)": "DnsRecordsets",
"routers": "NetworkRouters",
"security groups (network)": "NetworkSecurityGroups",
"security group rules (network)": "NetworkSecurityGroupRules",
Expand All @@ -118,6 +128,7 @@
"snapshots": "BlockStorageSnapshots",
"subnets (network)": "NetworkSubnets",
"volumes": "BlockStorageVolumes",
"users": "IdentityUsers"
"users": "IdentityUsers",
"zones (dns)": "DnsZones",
}
}
2 changes: 1 addition & 1 deletion openstack_tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ futures = { workspace = true }
itertools = { workspace = true }
json5 = "^0.4"
lazy_static = "^1.5"
openstack_sdk = { path = "../openstack_sdk", version = "^0.14", default-features = false, features = ["async", "block_storage", "compute", "identity", "image", "network"] }
openstack_sdk = { path = "../openstack_sdk", version = "^0.14", default-features = false, features = ["async", "block_storage", "compute", "dns", "identity", "image", "network"] }
pretty_assertions = "^1.4"
ratatui = { version = "^0.29", features = ["serde", "macros"] }
serde = { workspace = true }
Expand Down
12 changes: 11 additions & 1 deletion openstack_tui/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub enum Action {
/// Delete volume
DeleteBlockStorageVolume,

// Compute (neutron)
// Compute (Nova)
SetComputeServerFilters(cloud_types::ComputeServerFilters),
SetComputeServerInstanceActionFilters(cloud_types::ComputeServerInstanceActionFilters),
/// Show servers provisioned with selected flavor
Expand All @@ -99,6 +99,16 @@ pub enum Action {
/// Show selected server instance action events
ShowComputeServerInstanceActionEvents,

// DNS (Designate)
/// Set DNS Zone filters
SetDnsZoneFilters(cloud_types::DnsZoneFilters),
/// Delete DNS zone
DeleteDnsZone,
/// Set DNS Recordset filters
SetDnsRecordsetFilters(cloud_types::DnsRecordsetFilters),
/// Zone recordsets
ShowDnsZoneRecordsets,

// Identity (keystone)
// Groups
/// Create new identity group
Expand Down
4 changes: 4 additions & 0 deletions openstack_tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::{
},
confirm_popup::ConfirmPopup,
describe::Describe,
dns::{recordsets::DnsRecordsets, zones::DnsZones},
error_popup::ErrorPopup,
header::Header,
home::Home,
Expand Down Expand Up @@ -142,6 +143,9 @@ impl App {
Box::new(ComputeHypervisors::new()),
);

components.insert(Mode::DnsRecordsets, Box::new(DnsRecordsets::new()));
components.insert(Mode::DnsZones, Box::new(DnsZones::new()));

components.insert(
Mode::IdentityApplicationCredentials,
Box::new(IdentityApplicationCredentials::new()),
Expand Down
13 changes: 10 additions & 3 deletions openstack_tui/src/cloud_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ use crate::action::Action;
mod block_storage;
mod common;
mod compute;
mod dns;
mod identity;
mod image;
mod network;
pub mod types;

use crate::cloud_worker::{
block_storage::BlockStorageExt, compute::ComputeExt, identity::IdentityExt, image::ImageExt,
network::NetworkExt, types::*,
block_storage::BlockStorageExt, compute::ComputeExt, dns::DnsExt, identity::IdentityExt,
image::ImageExt, network::NetworkExt, types::*,
};

/// Cloud worker struct
Expand Down Expand Up @@ -63,10 +64,12 @@ impl Cloud {
session
.discover_service_endpoint(&openstack_sdk::types::ServiceType::Compute)
.await?;
session
.discover_service_endpoint(&openstack_sdk::types::ServiceType::Dns)
.await?;
session
.discover_service_endpoint(&openstack_sdk::types::ServiceType::Image)
.await?;

session
.discover_service_endpoint(&openstack_sdk::types::ServiceType::Network)
.await?;
Expand Down Expand Up @@ -159,6 +162,10 @@ impl Cloud {
<Cloud as ComputeExt>::perform_api_request(self, &app_tx, request)
.await?
}
ServiceType::Dns => {
<Cloud as DnsExt>::perform_api_request(self, &app_tx, request)
.await?
}
ServiceType::Identity => {
<Cloud as IdentityExt>::perform_api_request(self, &app_tx, request)
.await?
Expand Down
126 changes: 126 additions & 0 deletions openstack_tui/src/cloud_worker/dns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

use eyre::Result;
use serde_json::Value;
use tokio::sync::mpsc::UnboundedSender;

use openstack_sdk::{api::Pagination, api::QueryAsync};

use crate::action::Action;
use crate::cloud_worker::{ApiRequest, Cloud};

pub mod types;
use types::*;

pub trait DnsExt {
async fn perform_api_request(
&mut self,
app_tx: &UnboundedSender<Action>,
request: ApiRequest,
) -> Result<()>;

async fn get_recordsets(&mut self, filters: &DnsRecordsetFilters) -> Result<Vec<Value>>;
async fn get_zones(&mut self, filters: &DnsZoneFilters) -> Result<Vec<Value>>;
async fn delete_zone(&mut self, request: &DnsZoneDelete) -> Result<()>;
}

impl DnsExt for Cloud {
async fn perform_api_request(
&mut self,
app_tx: &UnboundedSender<Action>,
request: ApiRequest,
) -> Result<()> {
match request {
ApiRequest::DnsRecordsets(ref filters) => match self.get_recordsets(filters).await {
Ok(data) => app_tx.send(Action::ApiResponsesData { request, data })?,
Err(err) => app_tx.send(Action::Error(format!(
"Failed to fetch dns recordsets: {:?}",
err
)))?,
},
ApiRequest::DnsZones(ref filters) => match self.get_zones(filters).await {
Ok(data) => app_tx.send(Action::ApiResponsesData { request, data })?,
Err(err) => app_tx.send(Action::Error(format!(
"Failed to fetch dns zones: {:?}",
err
)))?,
},
ApiRequest::DnsZoneDelete(ref request) => match self.delete_zone(request).await {
Ok(_data) => app_tx.send(Action::Refresh)?,
Err(err) => app_tx.send(Action::Error(format!(
"Failed to delete dns zone: {:?}",
err
)))?,
},
_ => {
todo!()
}
}
Ok(())
}

async fn get_recordsets(&mut self, request: &DnsRecordsetFilters) -> Result<Vec<Value>> {
if let Some(session) = &self.cloud {
let res: Vec<Value> = if request.zone_id.is_some() {
let ep =
openstack_sdk::api::dns::v2::zone::recordset::list::RequestBuilder::try_from(
request,
)?
.build()?;
openstack_sdk::api::paged(ep, Pagination::All)
.query_async(session)
.await?
} else {
let ep = openstack_sdk::api::dns::v2::recordset::list::RequestBuilder::try_from(
request,
)?
.build()?;
openstack_sdk::api::paged(ep, Pagination::All)
.query_async(session)
.await?
};

return Ok(res);
}
Ok(Vec::new())
}

async fn get_zones(&mut self, _request: &DnsZoneFilters) -> Result<Vec<Value>> {
if let Some(session) = &self.cloud {
let mut ep_builder = openstack_sdk::api::dns::v2::zone::list::Request::builder();
ep_builder.sort_key("name");
ep_builder.sort_dir("asc");

let ep = ep_builder.build()?;
let res: Vec<Value> = openstack_sdk::api::paged(ep, Pagination::All)
.query_async(session)
.await?;
return Ok(res);
}
Ok(Vec::new())
}

async fn delete_zone(&mut self, request: &DnsZoneDelete) -> Result<()> {
if let Some(session) = &self.cloud {
let ep = openstack_sdk::api::dns::v2::zone::delete::Request::builder()
.id(request.zone_id.clone())
.build()?;

openstack_sdk::api::ignore(ep).query_async(session).await?;
return Ok(());
}
Ok(())
}
}
100 changes: 100 additions & 0 deletions openstack_tui/src/cloud_worker/dns/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};
use std::fmt;

use crate::cloud_worker::common::ConfirmableRequest;

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DnsRecordsetFilters {
/// Zone Id
pub zone_id: Option<String>,
/// Optional name (for info purposes
pub zone_name: Option<String>,
}
impl fmt::Display for DnsRecordsetFilters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut parts: Vec<String> = Vec::new();
if self.zone_id.is_some() || self.zone_name.is_some() {
parts.push(format!(
"zone: {}",
self.zone_name
.as_ref()
.or(self.zone_name.as_ref())
.unwrap_or(&String::new())
));
}
write!(f, "{}", parts.join(","))
}
}

impl TryFrom<&DnsRecordsetFilters>
for openstack_sdk::api::dns::v2::zone::recordset::list::RequestBuilder<'_>
{
type Error = eyre::Report;

fn try_from(value: &DnsRecordsetFilters) -> Result<Self, Self::Error> {
let mut ep_builder = openstack_sdk::api::dns::v2::zone::recordset::list::Request::builder();
ep_builder.sort_key("name");

if let Some(zone_id) = &value.zone_id {
ep_builder.zone_id(zone_id.clone());
}

Ok(ep_builder)
}
}

impl TryFrom<&DnsRecordsetFilters>
for openstack_sdk::api::dns::v2::recordset::list::RequestBuilder<'_>
{
type Error = eyre::Report;

fn try_from(_value: &DnsRecordsetFilters) -> Result<Self, Self::Error> {
let mut ep_builder = openstack_sdk::api::dns::v2::recordset::list::Request::builder();

ep_builder.sort_key("name");
Ok(ep_builder)
}
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DnsZoneFilters {}
impl fmt::Display for DnsZoneFilters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "")
}
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DnsZoneDelete {
pub zone_id: String,
pub zone_name: Option<String>,
}

impl fmt::Display for DnsZoneDelete {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "")
}
}

impl ConfirmableRequest for DnsZoneDelete {
fn get_confirm_message(&self) -> Option<String> {
Some(format!(
"Delete DNS Zone {} ?",
self.zone_name.clone().unwrap_or(self.zone_id.clone())
))
}
}
Loading