feat: current state
This commit is contained in:
+190
-2
@@ -1,2 +1,190 @@
|
||||
pub struct AzureDiscovery;
|
||||
pub struct AzureCliAdapter;
|
||||
use crate::{
|
||||
adapters::process::{CommandRunner, CommandSpec, KnownProgram},
|
||||
domain::{
|
||||
ids::{AzureResourceId, OperationId, SubscriptionId, TenantId},
|
||||
inventory::{DatabaseRef, FlexibleServer, Subscription, Tenant},
|
||||
},
|
||||
error::{AppError, SafeError},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{ffi::OsString, sync::Arc, time::Duration};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub struct AzureCliAdapter {
|
||||
runner: Arc<dyn CommandRunner>,
|
||||
}
|
||||
impl AzureCliAdapter {
|
||||
pub fn new(runner: Arc<dyn CommandRunner>) -> Self {
|
||||
Self { runner }
|
||||
}
|
||||
async fn call(
|
||||
&self,
|
||||
args: &[&str],
|
||||
op: OperationId,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Vec<u8>, AppError> {
|
||||
let mut all: Vec<OsString> = args.iter().map(OsString::from).collect();
|
||||
all.extend(
|
||||
["--only-show-errors", "--output", "json"]
|
||||
.iter()
|
||||
.map(OsString::from),
|
||||
);
|
||||
Ok(self
|
||||
.runner
|
||||
.run(
|
||||
CommandSpec {
|
||||
program: KnownProgram::AzureCli,
|
||||
args: all,
|
||||
timeout: Duration::from_secs(30),
|
||||
stdout_limit_bytes: 2 * 1024 * 1024,
|
||||
stderr_limit_bytes: 64 * 1024,
|
||||
operation_id: op,
|
||||
},
|
||||
cancellation,
|
||||
)
|
||||
.await?
|
||||
.stdout)
|
||||
}
|
||||
pub async fn current_tenant(
|
||||
&self,
|
||||
op: OperationId,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Tenant, AppError> {
|
||||
let raw = self.call(&["account", "show"], op, cancellation).await?;
|
||||
let dto: AccountDto =
|
||||
serde_json::from_slice(&raw).map_err(|_| AppError(SafeError::process()))?;
|
||||
Ok(Tenant {
|
||||
id: TenantId::new(dto.tenant_id).map_err(|_| AppError(SafeError::process()))?,
|
||||
display_name: dto.name.unwrap_or_else(|| "Current tenant".to_owned()),
|
||||
})
|
||||
}
|
||||
pub async fn subscriptions(
|
||||
&self,
|
||||
tenant: &TenantId,
|
||||
op: OperationId,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Vec<Subscription>, AppError> {
|
||||
let raw = self
|
||||
.call(&["account", "list", "--all"], op, cancellation)
|
||||
.await?;
|
||||
let dtos: Vec<AccountDto> =
|
||||
serde_json::from_slice(&raw).map_err(|_| AppError(SafeError::process()))?;
|
||||
Ok(dtos
|
||||
.into_iter()
|
||||
.filter(|d| d.tenant_id == tenant.as_str() && d.state.as_deref() == Some("Enabled"))
|
||||
.filter_map(|d| {
|
||||
Some(Subscription {
|
||||
id: SubscriptionId::new(d.id?).ok()?,
|
||||
display_name: d.name.unwrap_or_else(|| "Unnamed subscription".to_owned()),
|
||||
tenant_id: tenant.clone(),
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
pub async fn servers(
|
||||
&self,
|
||||
subscription: &Subscription,
|
||||
op: OperationId,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Vec<FlexibleServer>, AppError> {
|
||||
let raw = self
|
||||
.call(
|
||||
&[
|
||||
"postgres",
|
||||
"flexible-server",
|
||||
"list",
|
||||
"--subscription",
|
||||
subscription.id.as_str(),
|
||||
],
|
||||
op,
|
||||
cancellation,
|
||||
)
|
||||
.await?;
|
||||
let dtos: Vec<ServerDto> =
|
||||
serde_json::from_slice(&raw).map_err(|_| AppError(SafeError::process()))?;
|
||||
dtos.into_iter()
|
||||
.map(|d| {
|
||||
let id = AzureResourceId::new(d.id).map_err(|_| AppError(SafeError::process()))?;
|
||||
let group =
|
||||
resource_group(id.as_str()).ok_or_else(|| AppError(SafeError::process()))?;
|
||||
Ok(FlexibleServer {
|
||||
resource_id: id,
|
||||
subscription_id: subscription.id.clone(),
|
||||
resource_group: group,
|
||||
name: d.name,
|
||||
host: d.fully_qualified_domain_name,
|
||||
location: d.location.unwrap_or_default(),
|
||||
version: d.version,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub async fn databases(
|
||||
&self,
|
||||
server: &FlexibleServer,
|
||||
op: OperationId,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Vec<DatabaseRef>, AppError> {
|
||||
let raw = self
|
||||
.call(
|
||||
&[
|
||||
"postgres",
|
||||
"flexible-server",
|
||||
"db",
|
||||
"list",
|
||||
"--resource-group",
|
||||
&server.resource_group,
|
||||
"--server-name",
|
||||
&server.name,
|
||||
"--subscription",
|
||||
server.subscription_id.as_str(),
|
||||
],
|
||||
op,
|
||||
cancellation,
|
||||
)
|
||||
.await?;
|
||||
let dtos: Vec<DatabaseDto> =
|
||||
serde_json::from_slice(&raw).map_err(|_| AppError(SafeError::process()))?;
|
||||
dtos.into_iter()
|
||||
.map(|d| {
|
||||
let name = d.name;
|
||||
Ok(DatabaseRef {
|
||||
id: crate::domain::ids::DatabaseId::new(name.clone())
|
||||
.map_err(|_| AppError(SafeError::process()))?,
|
||||
server_id: crate::domain::ids::ServerId::new(server.resource_id.as_str())
|
||||
.map_err(|_| AppError(SafeError::process()))?,
|
||||
system: name == "postgres" || name.starts_with("azure_"),
|
||||
name,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountDto {
|
||||
id: Option<String>,
|
||||
tenant_id: String,
|
||||
name: Option<String>,
|
||||
state: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ServerDto {
|
||||
id: String,
|
||||
name: String,
|
||||
fully_qualified_domain_name: Option<String>,
|
||||
location: Option<String>,
|
||||
version: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct DatabaseDto {
|
||||
name: String,
|
||||
}
|
||||
fn resource_group(id: &str) -> Option<String> {
|
||||
let parts: Vec<&str> = id.split('/').collect();
|
||||
parts
|
||||
.windows(2)
|
||||
.find(|w| w[0].eq_ignore_ascii_case("resourceGroups"))
|
||||
.map(|w| w[1].to_owned())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user