@@ -10,6 +10,9 @@ use crate::config::{
1010} ;
1111use crate :: error:: Error ;
1212use crate :: User ;
13+ use std:: io;
14+ use std:: process:: { Command , Output } ;
15+
1316use tracing:: instrument;
1417
1518/// The interface for applying the desired configuration to the host.
@@ -68,21 +71,61 @@ impl Provision {
6871 . ok_or ( Error :: NoUserProvisioner )
6972 }
7073
74+ /// Sets the system hostname with a custom DHCP renewer function.
75+ ///
76+ /// This version allows dependency injection of the DHCP renewal command for testing.
77+ ///
78+ /// # Arguments
79+ ///
80+ /// * `dhcp_renew_command_runner` - A function that runs the DHCP renewal command and returns its output.
81+ ///
82+ /// # Returns
83+ ///
84+ /// Returns `Ok(())` if the hostname was set successfully and DHCP leases were renewed.
85+ /// Returns an error if hostname setting fails or DHCP renewal fails.
86+ ///
87+ #[ instrument( skip_all) ]
88+ pub fn set_hostname ( self ) -> Result < ( ) , Error > {
89+ #[ cfg( not( test) ) ]
90+ {
91+ self . set_hostname_with_dhcp_renewer ( || {
92+ Command :: new ( "systemctl" )
93+ . arg ( "restart" )
94+ . arg ( "systemd-networkd" )
95+ . output ( )
96+ } )
97+ }
98+ #[ cfg( test) ]
99+ {
100+ self . set_hostname_with_dhcp_renewer ( || {
101+ Ok ( Output {
102+ status : std:: os:: unix:: process:: ExitStatusExt :: from_raw ( 0 ) ,
103+ stdout : b"DHCP renewal successful" . to_vec ( ) ,
104+ stderr : b"" . to_vec ( ) ,
105+ } )
106+ } )
107+ }
108+ }
109+
71110 /// Sets the system hostname using the hostname found in either IMDS or the OVF.
72111 ///
73112 /// This function iterates over a list of available backends and attempts to
74113 /// set the hostname until one succeeds. Currently supported backends include:
75114 /// - `Hostnamectl`
76115 ///
77116 /// Additional hostname provisioners can be set through config files.
117+ /// This function ends by renewing all DHCP leases.
78118 ///
79119 /// # Returns
80120 ///
81121 /// Returns `Ok(())` if the hostname was set successfully by any of the backends.
82122 /// Returns `Err(Error::NoHostnameProvisioner)` if no backends was able to set
83123 /// the hostname.
84124 #[ instrument( skip_all) ]
85- pub fn set_hostname ( self ) -> Result < ( ) , Error > {
125+ fn set_hostname_with_dhcp_renewer (
126+ self ,
127+ dhcp_renew_command_runner : impl Fn ( ) -> io:: Result < Output > ,
128+ ) -> Result < ( ) , Error > {
86129 self . config
87130 . hostname_provisioners
88131 . backends
@@ -94,7 +137,41 @@ impl Provision {
94137 #[ cfg( test) ]
95138 HostnameProvisioner :: FakeHostnamectl => Some ( ( ) ) ,
96139 } )
97- . ok_or ( Error :: NoHostnameProvisioner )
140+ . ok_or ( Error :: NoHostnameProvisioner ) ?;
141+
142+ Self :: renew_dhcp_leases_with_runner ( dhcp_renew_command_runner) ?;
143+
144+ Ok ( ( ) )
145+ }
146+
147+ /// Renews DHCP leases using a custom command runner.
148+ ///
149+ /// # Arguments
150+ ///
151+ /// * `dhcp_renew_command_runner` - A function that runs the DHCP renewal command and returns its output.
152+ ///
153+ /// # Returns
154+ ///
155+ /// Returns `Ok(())` if DHCP leases were renewed successfully.
156+ /// Returns an error if the command fails.
157+ #[ instrument( skip_all) ]
158+ fn renew_dhcp_leases_with_runner (
159+ dhcp_renew_command_runner : impl Fn ( ) -> io:: Result < Output > ,
160+ ) -> Result < ( ) , Error > {
161+ let output = dhcp_renew_command_runner ( ) ?;
162+
163+ if !output. status . success ( ) {
164+ tracing:: error!(
165+ "Failed to renew DHCP leases: {}" ,
166+ String :: from_utf8_lossy( & output. stderr)
167+ ) ;
168+ return Err ( Error :: SubprocessFailed {
169+ command : "networkctl renew" . to_string ( ) ,
170+ status : output. status ,
171+ } ) ;
172+ }
173+
174+ Ok ( ( ) )
98175 }
99176
100177 /// Provisioning can fail if the host lacks the necessary tools. For example,
0 commit comments