Skip to content

Commit

Permalink
Merge pull request #622 from openSUSE/network-cli
Browse files Browse the repository at this point in the history
Added support for showing the network configuration through the CLI
  • Loading branch information
teclator authored Jun 21, 2023
2 parents a820318 + 6cc4084 commit 83b5d7b
Show file tree
Hide file tree
Showing 16 changed files with 513 additions and 27 deletions.
15 changes: 15 additions & 0 deletions rust/agama-lib/share/examples/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,20 @@
"root": {
"password": "nots3cr3t",
"sshKey": "..."
},
"network": {
"connections": [
{
"name": "Ethernet network device 1",
"method": "manual",
"addresses": [
"192.168.122.100/24"
],
"gateway": "192.168.122.1",
"nameservers": [
"192.168.122.1"
]
}
]
}
}
23 changes: 23 additions & 0 deletions rust/agama-lib/share/examples/profile.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,27 @@ local findBiggestDisk(disks) =
},
],
},
network: {
connections: [
{
name: 'AgamaNetwork',
wireless: {
password: 'agama.test',
security: 'wpa-psk',
ssid: 'AgamaNetwork'
}
},
{
name: 'Etherned device 1',
method: 'manual',
gateway: '192.168.122.1',
addresses: [
'192.168.122.100/24,'
],
nameservers: [
'1.2.3.4'
]
]
}
]
}
78 changes: 78 additions & 0 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,84 @@
}
}
},
"network": {
"description": "Network settings",
"type": "object",
"properties": {
"connections": {
"description": "Network connections to be defined",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"description": "Connection ID",
"type": "string"
},
"method": {
"description": "IPv4 configuration method (e.g., 'auto')",
"type": "string",
"enum": [
"auto",
"manual",
"link-local",
"disabled"
]
},
"gateway": {
"description": "Connection gateway address (e.g. 192.168.122.1)",
"type": "string"
},
"addresses": {
"type": "array",
"items": {
"description": "Connection addresses",
"type": "string",
"additionalProperties": false
}
},
"nameservers": {
"type": "array",
"items": {
"description": "Connection nameservers",
"type": "string",
"additionalProperties": false
}
},
"wireless": {
"type": "object",
"description": "Wireless configuration",
"additionalProperties": false,
"properties": {
"password": {
"type": "string"
},
"security": {
"type": "string"
},
"ssid": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"infrastructure",
"adhoc",
"mesh",
"ap"
]
}
}
}
},
"required": [
"name"
]
}
}
}
},
"user": {
"description": "First user settings",
"type": "object",
Expand Down
31 changes: 29 additions & 2 deletions rust/agama-lib/src/install_settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Configuration settings handling
//!
//! This module implements the mechanisms to load and store the installation settings.
use crate::network::NetworkSettings;
use crate::settings::{SettingObject, SettingValue, Settings};
use agama_derive::Settings;
use serde::{Deserialize, Serialize};
Expand All @@ -20,14 +21,21 @@ pub enum Scope {
Software,
/// Storage settings
Storage,
/// Network settings
Network,
}

impl Scope {
/// Returns known scopes
///
// TODO: we can rely on strum so we do not forget to add them
pub fn all() -> [Scope; 3] {
[Scope::Software, Scope::Storage, Scope::Users]
pub fn all() -> [Scope; 4] {
[
Scope::Network,
Scope::Software,
Scope::Storage,
Scope::Users,
]
}
}

Expand All @@ -39,6 +47,7 @@ impl FromStr for Scope {
"users" => Ok(Self::Users),
"software" => Ok(Self::Software),
"storage" => Ok(Self::Storage),
"network" => Ok(Self::Network),
_ => Err("Unknown section"),
}
}
Expand All @@ -57,6 +66,8 @@ pub struct InstallSettings {
pub software: Option<SoftwareSettings>,
#[serde(default)]
pub storage: Option<StorageSettings>,
#[serde(default)]
pub network: Option<NetworkSettings>,
}

impl InstallSettings {
Expand All @@ -73,6 +84,9 @@ impl InstallSettings {
if self.software.is_some() {
scopes.push(Scope::Software);
}
if self.network.is_some() {
scopes.push(Scope::Network);
}
scopes
}
}
Expand All @@ -81,6 +95,10 @@ impl Settings for InstallSettings {
fn add(&mut self, attr: &str, value: SettingObject) -> Result<(), &'static str> {
if let Some((ns, id)) = attr.split_once('.') {
match ns {
"network" => {
let network = self.network.get_or_insert(Default::default());
network.add(id, value)?
}
"software" => {
let software = self.software.get_or_insert(Default::default());
software.add(id, value)?
Expand All @@ -102,6 +120,10 @@ impl Settings for InstallSettings {
fn set(&mut self, attr: &str, value: SettingValue) -> Result<(), &'static str> {
if let Some((ns, id)) = attr.split_once('.') {
match ns {
"network" => {
let network = self.network.get_or_insert(Default::default());
network.set(id, value)?
}
"software" => {
let software = self.software.get_or_insert(Default::default());
software.set(id, value)?
Expand All @@ -127,6 +149,11 @@ impl Settings for InstallSettings {
}

fn merge(&mut self, other: &Self) {
if let Some(other_network) = &other.network {
let network = self.network.get_or_insert(Default::default());
network.merge(other_network);
}

if let Some(other_software) = &other.software {
let software = self.software.get_or_insert(Default::default());
software.merge(other_software);
Expand Down
1 change: 1 addition & 0 deletions rust/agama-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod error;
pub mod install_settings;
pub mod manager;
pub mod network;
pub mod profile;
pub mod settings;
pub mod software;
Expand Down
9 changes: 9 additions & 0 deletions rust/agama-lib/src/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod client;
mod model;
mod proxies;
mod store;
pub mod types;

pub use client::NetworkClient;
pub use model::NetworkSettings;
pub use store::NetworkStore;
104 changes: 104 additions & 0 deletions rust/agama-lib/src/network/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::model::{NetworkConnection, WirelessSettings};
use super::types::SSID;
use crate::error::ServiceError;

use super::proxies::ConnectionProxy;
use super::proxies::ConnectionsProxy;
use super::proxies::IPv4Proxy;
use super::proxies::WirelessProxy;
use zbus::Connection;

/// D-BUS client for the network service
pub struct NetworkClient<'a> {
pub connection: Connection,
connections_proxy: ConnectionsProxy<'a>,
}

impl<'a> NetworkClient<'a> {
pub async fn new(connection: Connection) -> Result<NetworkClient<'a>, ServiceError> {
Ok(Self {
connections_proxy: ConnectionsProxy::new(&connection).await?,
connection,
})
}

/// Returns an array of network connections
pub async fn connections(&self) -> Result<Vec<NetworkConnection>, ServiceError> {
let connection_paths = self.connections_proxy.get_connections().await?;
let mut connections = vec![];

for path in connection_paths {
let mut connection = self.connection_from(path.as_str()).await?;

if let Ok(wireless) = self.wireless_from(path.as_str()).await {
connection.wireless = Some(wireless);
}

connections.push(connection);
}

Ok(connections)
}

/// Returns the NetworkConnection for the given connection path
///
/// * `path`: the connections path to get the config from
async fn connection_from(&self, path: &str) -> Result<NetworkConnection, ServiceError> {
let connection_proxy = ConnectionProxy::builder(&self.connection)
.path(path)?
.build()
.await?;
let name = connection_proxy.id().await?;

let ipv4_proxy = IPv4Proxy::builder(&self.connection)
.path(path)?
.build()
.await?;

/// TODO: consider using the `IPMethod` struct from `agama-network`.
let method = match ipv4_proxy.method().await? {
0 => "auto",
1 => "manual",
2 => "link-local",
3 => "disable",
_ => "auto",
};
let gateway = match ipv4_proxy.gateway().await?.as_str() {
"" => None,
value => Some(value.to_string()),
};
let nameservers = ipv4_proxy.nameservers().await?;
let addresses = ipv4_proxy.addresses().await?;
let addresses = addresses
.into_iter()
.map(|(ip, prefix)| format!("{ip}/{prefix}"))
.collect();

Ok(NetworkConnection {
name,
method: method.to_string(),
gateway,
addresses,
nameservers,
..Default::default()
})
}

/// Returns the [wireless settings][WirelessSettings] for the given connection
///
/// * `path`: the connections path to get the wireless config from
async fn wireless_from(&self, path: &str) -> Result<WirelessSettings, ServiceError> {
let wireless_proxy = WirelessProxy::builder(&self.connection)
.path(path)?
.build()
.await?;
let wireless = WirelessSettings {
mode: wireless_proxy.mode().await?,
password: wireless_proxy.password().await?,
security: wireless_proxy.security().await?,
ssid: SSID(wireless_proxy.ssid().await?).to_string(),
};

Ok(wireless)
}
}
Loading

0 comments on commit 83b5d7b

Please sign in to comment.