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
30 changes: 24 additions & 6 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"agama-autoinstall",
"agama-cli",
"agama-files",
"agama-hostname",
"agama-l10n",
"agama-lib",
"agama-locale-data",
Expand Down
21 changes: 21 additions & 0 deletions rust/agama-hostname/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "agama-hostname"
version = "0.1.0"
rust-version.workspace = true
edition.workspace = true

[dependencies]
agama-utils = { version = "0.1.0", path = "../agama-utils" }
anyhow = "1.0.100"
async-trait = "0.1.89"
tempfile = "3.23.0"
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "sync"] }
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tracing = "0.1.43"
zbus = "5.12.0"

[dev-dependencies]
tempfile = "3.23.0"
test-context = "0.4.1"
155 changes: 155 additions & 0 deletions rust/agama-hostname/src/dbus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! # D-Bus interface proxy for: `org.freedesktop.hostname1`
//!
//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data.
//! Source: `Interface '/org/freedesktop/hostname1' from service 'org.freedesktop.hostname1' on system bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
//! More information can be found in the [Writing a client proxy] section of the zbus
//! documentation.
//!
//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the
//! following zbus API can be used:
//!
//! * [`zbus::fdo::PeerProxy`]
//! * [`zbus::fdo::IntrospectableProxy`]
//! * [`zbus::fdo::PropertiesProxy`]
//!
//! Consequently `zbus-xmlgen` did not generate code for the above interfaces.
//!
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
use zbus::proxy;
#[proxy(
interface = "org.freedesktop.hostname1",
default_service = "org.freedesktop.hostname1",
default_path = "/org/freedesktop/hostname1"
)]
pub trait Hostname1 {
/// Describe method
fn describe(&self) -> zbus::Result<String>;

/// GetHardwareSerial method
fn get_hardware_serial(&self) -> zbus::Result<String>;

/// GetProductUUID method
#[zbus(name = "GetProductUUID")]
fn get_product_uuid(&self, interactive: bool) -> zbus::Result<Vec<u8>>;

/// SetChassis method
fn set_chassis(&self, chassis: &str, interactive: bool) -> zbus::Result<()>;

/// SetDeployment method
fn set_deployment(&self, deployment: &str, interactive: bool) -> zbus::Result<()>;

/// SetHostname method
fn set_hostname(&self, hostname: &str, interactive: bool) -> zbus::Result<()>;

/// SetIconName method
fn set_icon_name(&self, icon: &str, interactive: bool) -> zbus::Result<()>;

/// SetLocation method
fn set_location(&self, location: &str, interactive: bool) -> zbus::Result<()>;

/// SetPrettyHostname method
fn set_pretty_hostname(&self, hostname: &str, interactive: bool) -> zbus::Result<()>;

/// SetStaticHostname method
fn set_static_hostname(&self, hostname: &str, interactive: bool) -> zbus::Result<()>;

/// BootID property
#[zbus(property, name = "BootID")]
fn boot_id(&self) -> zbus::Result<Vec<u8>>;

/// Chassis property
#[zbus(property)]
fn chassis(&self) -> zbus::Result<String>;

/// DefaultHostname property
#[zbus(property)]
fn default_hostname(&self) -> zbus::Result<String>;

/// Deployment property
#[zbus(property)]
fn deployment(&self) -> zbus::Result<String>;

/// FirmwareDate property
#[zbus(property)]
fn firmware_date(&self) -> zbus::Result<u64>;

/// FirmwareVendor property
#[zbus(property)]
fn firmware_vendor(&self) -> zbus::Result<String>;

/// FirmwareVersion property
#[zbus(property)]
fn firmware_version(&self) -> zbus::Result<String>;

/// HardwareModel property
#[zbus(property)]
fn hardware_model(&self) -> zbus::Result<String>;

/// HardwareVendor property
#[zbus(property)]
fn hardware_vendor(&self) -> zbus::Result<String>;

/// HomeURL property
#[zbus(property, name = "HomeURL")]
fn home_url(&self) -> zbus::Result<String>;

/// Hostname property
#[zbus(property)]
fn hostname(&self) -> zbus::Result<String>;

/// HostnameSource property
#[zbus(property)]
fn hostname_source(&self) -> zbus::Result<String>;

/// IconName property
#[zbus(property)]
fn icon_name(&self) -> zbus::Result<String>;

/// KernelName property
#[zbus(property)]
fn kernel_name(&self) -> zbus::Result<String>;

/// KernelRelease property
#[zbus(property)]
fn kernel_release(&self) -> zbus::Result<String>;

/// KernelVersion property
#[zbus(property)]
fn kernel_version(&self) -> zbus::Result<String>;

/// Location property
#[zbus(property)]
fn location(&self) -> zbus::Result<String>;

/// MachineID property
#[zbus(property, name = "MachineID")]
fn machine_id(&self) -> zbus::Result<Vec<u8>>;

/// OperatingSystemCPEName property
#[zbus(property, name = "OperatingSystemCPEName")]
fn operating_system_cpename(&self) -> zbus::Result<String>;

/// OperatingSystemPrettyName property
#[zbus(property)]
fn operating_system_pretty_name(&self) -> zbus::Result<String>;

/// OperatingSystemSupportEnd property
#[zbus(property)]
fn operating_system_support_end(&self) -> zbus::Result<u64>;

/// PrettyHostname property
#[zbus(property)]
fn pretty_hostname(&self) -> zbus::Result<String>;

/// StaticHostname property
#[zbus(property)]
fn static_hostname(&self) -> zbus::Result<String>;

/// VSockCID property
#[zbus(property, name = "VSockCID")]
fn vsock_cid(&self) -> zbus::Result<u32>;
}
114 changes: 114 additions & 0 deletions rust/agama-hostname/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) [2025] SUSE LLC
//
// All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, contact SUSE LLC.
//
// To contact SUSE LLC about this file by physical or electronic mail, you may
// find current contact information at www.suse.com.

//! This crate implements the support for hostname handling in Agama.
//! It takes care of setting the hostname for Agama itself and copying it
//! to the target system in case of an static one.
//!
//! * The [Proposal] struct that describes how the system will look like after
//! the installation.
//! * The [SystemInfo] which includes information about the system
//! where Agama is running.
//! * An [specific event type](Event) for hostname-related events.
//!
//! The service can be started by calling the [start_service] function, which
//! returns a [agama_utils::actors::ActorHandler] to interact with the system.

pub mod service;
pub use service::{Service, Starter};

mod dbus;
pub mod message;
mod model;
pub use model::{Model, ModelAdapter};
mod monitor;
pub mod test_utils;

#[cfg(test)]
mod tests {
use crate::{message, service::Service, test_utils::start_service};

use agama_utils::{
actor::Handler,
api::{self, event::Event, scope::Scope},
issue,
};
use test_context::{test_context, AsyncTestContext};
use tokio::sync::broadcast;

struct Context {
events_rx: broadcast::Receiver<Event>,
handler: Handler<Service>,
issues: Handler<issue::Service>,
}

impl AsyncTestContext for Context {
async fn setup() -> Context {
let (events_tx, events_rx) = broadcast::channel::<Event>(16);
let issues = issue::Service::starter(events_tx.clone()).start();

let handler = start_service(events_tx, issues.clone()).await;

Self {
events_rx,
handler,
issues,
}
}
}

#[test_context(Context)]
#[tokio::test]
async fn test_get_and_set_config(ctx: &mut Context) -> Result<(), Box<dyn std::error::Error>> {
let mut config = ctx.handler.call(message::GetConfig).await.unwrap();
assert_eq!(config.r#static, Some("test-hostname".to_string()));
config.r#static = Some("".to_string());
config.hostname = Some("test".to_string());

ctx.handler.call(message::SetConfig::with(config)).await?;

let updated = ctx.handler.call(message::GetConfig).await?;
assert_eq!(
&updated,
&api::hostname::Config {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np: If you want, you could implement something like:

Config::new()
  with_hostname("test");

r#static: Some("".to_string()),
hostname: Some("test".to_string())
}
);

let proposal = ctx.handler.call(message::GetProposal).await?;
assert!(proposal.is_some());

let event = ctx
.events_rx
.recv()
.await
.expect("Did not receive the event");

assert!(matches!(
event,
Event::ProposalChanged {
scope: Scope::Hostname
}
));

Ok(())
}
}
Loading
Loading