diff --git a/app/models/nic/base.rb b/app/models/nic/base.rb
index b0e0c3f06ed..70bef808a53 100644
--- a/app/models/nic/base.rb
+++ b/app/models/nic/base.rb
@@ -119,9 +119,9 @@ class Base < ApplicationRecord
end
class Jail < ::Safemode::Jail
allow :id, :subnet, :subnet6, :virtual?, :physical?, :mac, :ip, :ip6, :identifier, :attached_to,
- :link, :tag, :domain, :vlanid, :mtu, :bond_options, :attached_devices, :mode,
- :attached_devices_identifiers, :primary, :provision, :alias?, :inheriting_mac,
- :children_mac_addresses, :nic_delay, :fqdn, :shortname, :type, :managed?, :bond?, :bridge?, :bmc?
+ :link, :tag, :domain, :bond_options, :attached_devices, :mode,
+ :attached_devices_identifiers, :primary, :provision, :inheriting_mac,
+ :children_mac_addresses, :nic_delay, :fqdn, :shortname, :type, :managed?, :bond?, :bmc?, :provision?
end
# include STI inheritance column in audits
diff --git a/app/models/nic/interface.rb b/app/models/nic/interface.rb
index a8dec6d6601..4c8fa312404 100644
--- a/app/models/nic/interface.rb
+++ b/app/models/nic/interface.rb
@@ -26,6 +26,10 @@ class Interface < Base
alias_method :network, :subnet_network
alias_method :network6, :subnet6_network
+ class Jail < Nic::Base::Jail
+ allow :mtu, :vlanid, :bridge?, :alias?
+ end
+
def vlanid
# Determine a vlanid according to the following cascading rules:
# 1. if the interface has a tag, use that as the vlanid
diff --git a/app/services/foreman/template_snapshot_service.rb b/app/services/foreman/template_snapshot_service.rb
index 4287b68be05..abdbf2a4de6 100644
--- a/app/services/foreman/template_snapshot_service.rb
+++ b/app/services/foreman/template_snapshot_service.rb
@@ -114,6 +114,7 @@ def host4dhcp
name: 'snapshot-ipv4-dhcp-el7',
subnet: FactoryBot.build(:subnet_ipv4_dhcp_for_snapshots),
interfaces: [ipv4_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -122,6 +123,7 @@ def host4static
name: 'snapshot-ipv4-static-el7',
subnet: FactoryBot.build(:subnet_ipv4_static_for_snapshots),
interfaces: [ipv4_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -130,6 +132,7 @@ def host6dhcp
name: 'snapshot-ipv6-dhcp-el7',
subnet: FactoryBot.build(:subnet_ipv6_dhcp_for_snapshots),
interfaces: [ipv6_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -138,6 +141,7 @@ def host6static
name: 'snapshot-ipv6-static-el7',
subnet: FactoryBot.build(:subnet_ipv6_static_for_snapshots),
interfaces: [ipv6_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -147,6 +151,7 @@ def host4and6dhcp
subnet: FactoryBot.build(:subnet_ipv4_dhcp_for_snapshots),
subnet6: FactoryBot.build(:subnet_ipv6_dhcp_for_snapshots),
interfaces: [ipv46_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -155,6 +160,7 @@ def debian4dhcp
name: 'snapshot-ipv4-dhcp-deb10',
subnet: FactoryBot.build(:subnet_ipv4_dhcp_for_snapshots),
interfaces: [ipv4_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
@@ -179,6 +185,7 @@ def rhel9_dhcp
name: 'snapshot-ipv4-dhcp-rhel9',
subnet: FactoryBot.build(:subnet_ipv4_dhcp_for_snapshots),
interfaces: [ipv4_interface])
+ host.stubs(:managed_interfaces).returns(host.interfaces)
define_host_params(host)
end
diff --git a/app/views/unattended/provisioning_templates/finish/kickstart_default_finish.erb b/app/views/unattended/provisioning_templates/finish/kickstart_default_finish.erb
index 8fe6bcd3a15..f4d5c983590 100644
--- a/app/views/unattended/provisioning_templates/finish/kickstart_default_finish.erb
+++ b/app/views/unattended/provisioning_templates/finish/kickstart_default_finish.erb
@@ -33,14 +33,6 @@ description: |
%>
<%= snippet_if_exists(template_name + " custom pre") -%>
-<% if @host.subnet.respond_to?(:dhcp_boot_mode?) -%>
-<%= snippet 'kickstart_networking_setup' %>
-<% if (rhel_compatible && os_major >= 8) -%>
-systemctl restart NetworkManager
-<% else -%>
-service network restart
-<% end -%>
-<% end -%>
<% if @host.provision_method == 'image' && root_pass.present? -%>
# Install the root password
diff --git a/app/views/unattended/provisioning_templates/provision/kickstart_default.erb b/app/views/unattended/provisioning_templates/provision/kickstart_default.erb
index 429191f1e28..5c4c9a17c9e 100644
--- a/app/views/unattended/provisioning_templates/provision/kickstart_default.erb
+++ b/app/views/unattended/provisioning_templates/provision/kickstart_default.erb
@@ -114,85 +114,22 @@ selinux --<%= host_param('selinux-mode') || host_param('selinux') || 'enforcing'
keyboard <%= host_param('keyboard') || 'us' %>
<%
- network_options = []
- nameservers = []
- subnet4 = iface.subnet
- subnet6 = iface.subnet6
-
- # device and hostname
- if iface.bond? && rhel_compatible && os_major >= 6
- network_options.push("--device=#{iface.identifier}")
- else
- network_options.push("--device=#{iface.mac || iface.identifier}")
- end
- network_options.push("--hostname #{@host.name}")
-
- # single stack
- if subnet4 && !subnet6
- network_options.push("--noipv6")
- elsif !subnet4 && subnet6
- network_options.push("--noipv4")
- end
-
- # dual stack MTU check
- raise("IPv4 and IPv6 subnets have different MTU") if subnet4 && subnet6 && subnet4.mtu.present? && subnet6.mtu.present? && subnet4.mtu != subnet6.mtu
-
- # IPv4
- if (subnet4 && !subnet4.dhcp_boot_mode?) || @static
- network_options.push("--bootproto static")
- network_options.push("--ip=#{iface.ip}")
- network_options.push("--netmask=#{subnet4.mask}")
- network_options.push("--gateway=#{subnet4.gateway}")
- elsif subnet4 && subnet4.dhcp_boot_mode?
- network_options.push("--bootproto dhcp")
- end
- if subnet4
- nameservers.concat(subnet4.dns_servers)
- network_options.push("--mtu=#{subnet4.mtu}") if subnet4.mtu.present?
- end
-
- # IPv6
- if rhel_compatible && os_major >= 6
- if (subnet6 && !subnet6.dhcp_boot_mode?) || @static6
- network_options.push("--ipv6=#{iface.ip6}/#{subnet6.cidr}")
- network_options.push("--ipv6gateway=#{subnet6.gateway}")
- elsif subnet6 && subnet6.dhcp_boot_mode?
- if host_param_true?('use-slaac')
- network_options.push("--ipv6 auto")
- else
- network_options.push("--ipv6 dhcp")
- end
- end
- if subnet6
- nameservers.concat(subnet6.dns_servers)
- network_options.push("--mtu=#{subnet6.mtu}") if subnet6.mtu.present?
- end
- end
-
- # bond
- if iface.bond? && rhel_compatible && os_major >= 6
- bond_slaves = iface.attached_devices_identifiers.join(',')
- network_options.push("--bondslaves=#{bond_slaves}")
- network_options.push("--bondopts=mode=#{iface.mode},#{iface.bond_options.tr(' ', ',')}")
- end
-
- # VLAN (only on physical is recognized)
- if iface.virtual? && iface.tag.present? && iface.attached_to.present?
- if rhel_compatible && os_major == 6
- network_options.push("--vlanid=#{iface.tag}")
- else
- network_options.push("--interfacename=vlan#{iface.tag}")
- end
- end
-
- # DNS
- if nameservers.size > 0
- network_options.push("--nameserver=#{nameservers.join(',')}")
- else
- network_options.push("--nodns")
- end
+# start with provisioning interface, then other non-bond non-bridge interfaces and the bonds + bridges at the end
+@host.interfaces.reject{ |iface| iface.bmc? }.sort_by { |iface| (iface.bond? || iface.bridge?) ? 0 : iface.provision? ? 20 : 10 }.each do |iface|
+-%>
+<%= snippet(
+ 'kickstart_network_interface',
+ variables: {
+ iface: iface,
+ host: @host,
+ use_slaac: host_param_true?('use-slaac'),
+ static: @static,
+ static6: @static6
+ }
+ ) -%>
+<%
+end
-%>
-network <%= network_options.join(' ') %>
rootpw --iscrypted <%= root_pass %>
<% if host_param_true?('disable-firewall') -%>
@@ -300,8 +237,6 @@ exec < /dev/tty3 > /dev/tty3
chvt 3
(
logger "Starting anaconda <%= @host %> postinstall"
-<%= snippet 'kickstart_networking_setup' %>
-
<%= snippet 'ntp' %>
<%= snippet 'yum_proxy' %>
diff --git a/app/views/unattended/provisioning_templates/provision/kickstart_ovirt.erb b/app/views/unattended/provisioning_templates/provision/kickstart_ovirt.erb
index f548795c93b..f3176a41f90 100644
--- a/app/views/unattended/provisioning_templates/provision/kickstart_ovirt.erb
+++ b/app/views/unattended/provisioning_templates/provision/kickstart_ovirt.erb
@@ -51,13 +51,24 @@ end
liveimg --url=<%= liveimg_url %>
-<% subnet = @host.subnet -%>
-<% if subnet.respond_to?(:dhcp_boot_mode?) -%>
-<% dhcp = subnet.dhcp_boot_mode? && !@static -%>
-<% else -%>
-<% dhcp = !@static -%>
-<% end -%>
-network --bootproto <%= dhcp ? 'dhcp' : "static --ip=#{@host.ip} --netmask=#{subnet.mask} --gateway=#{subnet.gateway} --nameserver=#{subnet.dns_servers.join(',')}" %> --hostname <%= @host %> --device=<%= @host.mac -%>
+<%
+# start with provisioning interface, then other non-bond interfaces and the bonds at the end
+@host.managed_interfaces.sort_by { |iface| (iface.bond? || iface.bridge?) 0 : iface.provision? 20 : 10 }.each |iface| do
+%>
+ <%= snippet(
+ 'kickstart_network_interface',
+ variables: {
+ iface: iface,
+ host: @host,
+ use_slaac: false,
+ static: @static,
+ static6: @static6
+ }
+ ) -%>
+<%
+end
+-%>
+
rootpw --iscrypted <%= root_pass %>
<% if host_param_true?('disable-firewall') -%>
@@ -77,7 +88,6 @@ reboot
%post --log=/root/ks.post.log --erroronfail
nodectl init
<%= snippet 'redhat_register' %>
-<%= snippet 'kickstart_networking_setup' %>
<%= snippet 'efibootmgr_netboot' %>
<% if host_param_true?('host_registration_insights') -%>
<%= snippet 'insights' -%>
diff --git a/app/views/unattended/provisioning_templates/snippet/kickstart_network_interface.erb b/app/views/unattended/provisioning_templates/snippet/kickstart_network_interface.erb
new file mode 100644
index 00000000000..9c173090e41
--- /dev/null
+++ b/app/views/unattended/provisioning_templates/snippet/kickstart_network_interface.erb
@@ -0,0 +1,108 @@
+<%#
+name: kickstart_network_interface
+model: ProvisioningTemplate
+snippet: true
+model: ProvisioningTemplate
+kind: snippet
+description: |
+ Generates network directive for a given interface. It is not expected to be used directly.
+-%>
+<%#
+ # Variables: iface, host, use_slaac, static, static6
+-%>
+<%if @iface.managed? -%>
+<%
+network_options = []
+nameservers = []
+subnet4 = @iface.subnet
+subnet6 = @iface.subnet6
+
+rhel_compatible = @host.operatingsystem.family == 'Redhat' && @host.operatingsystem.name != 'Fedora'
+is_fedora = @host.operatingsystem.name == 'Fedora'
+os_major = @host.operatingsystem.major.to_i
+
+# device and hostname
+if @iface.bond? || @iface.bridge?
+ network_options.push("--device=#{@iface.identifier}")
+else
+ network_options.push("--device=#{@iface.mac || @iface.identifier}")
+end
+network_options.push("--hostname #{@host.name}")
+
+# single stack
+network_options.push("--noipv6") if subnet4 && !subnet6
+network_options.push("--noipv4") if !subnet4 && subnet6
+
+# dual stack MTU check
+raise("IPv4 and IPv6 subnets have different MTU") if subnet4 && subnet6 && subnet4.mtu.present? && subnet6.mtu.present? && subnet4.mtu != subnet6.mtu
+
+# mtu method is taking into account both ipv4 and ipv6 stacks
+if @iface.respond_to?(:mtu) && @iface.mtu
+ network_options.push("--mtu=#{@iface.mtu}")
+end
+
+# IPv4
+if subnet4
+ if !subnet4.dhcp_boot_mode? || @static
+ network_options.push("--bootproto static")
+ network_options.push("--ip=#{@iface.ip}")
+ network_options.push("--netmask=#{subnet4.mask}")
+ network_options.push("--gateway=#{subnet4.gateway}")
+ elsif subnet4.dhcp_boot_mode?
+ network_options.push("--bootproto dhcp")
+ end
+
+ nameservers.concat(subnet4.dns_servers)
+end
+
+# IPv6
+if subnet6
+ if !subnet6.dhcp_boot_mode? || @static6
+ network_options.push("--ipv6=#{@iface.ip6}/#{subnet6.cidr}")
+ network_options.push("--ipv6gateway=#{subnet6.gateway}")
+ elsif subnet6.dhcp_boot_mode?
+ if @use_slaac
+ network_options.push("--ipv6 auto")
+ else
+ network_options.push("--ipv6 dhcp")
+ end
+ end
+
+ nameservers.concat(subnet6.dns_servers)
+end
+
+# bond
+if @iface.bond?
+ bond_slaves = @iface.attached_devices_identifiers.join(',')
+ network_options.push("--bondslaves=#{bond_slaves}")
+ network_options.push("--bondopts=mode=#{@iface.mode},#{@iface.bond_options.tr(' ', ',')}")
+end
+
+# bridge
+if @iface.bridge?
+ bridge_slaves = @iface.attached_devices_identifiers.join(',')
+ network_options.push("--bridgeslaves=#{bridge_slaves}")
+ # Currently no support for bridge options in the interface.
+end
+
+# VLAN (only on physical is recognized)
+if @iface.virtual? && @iface.tag.present? && @iface.attached_to.present?
+ network_options.push("--vlanid=#{@iface.tag}")
+ network_options.push("--interfacename=vlan#{@iface.tag}") if (is_fedora && os_major >= 21) || (rhel_compatible && os_major >= 7)
+end
+
+# DNS
+if nameservers.empty?
+ network_options.push("--nodns")
+else
+ network_options.push("--nameserver=#{nameservers.join(',')}")
+end
+
+# Search domain - available from Fedora 39 (RHEL 10)
+if @iface.domain && ((is_fedora && os_major >= 39) || (rhel_compatible && os_major >= 10))
+ network_options.push("--ipv4-dns-search=#{@iface.domain}") if subnet4
+ network_options.push("--ipv6-dns-search=#{@iface.domain}") if subnet6
+end
+-%>
+<%= "network #{network_options.join(' ')}\n" -%>
+<% end -%>
diff --git a/config/initializers/safemode_jail.rb b/config/initializers/safemode_jail.rb
index 34576c8ac49..cd67430bdb0 100644
--- a/config/initializers/safemode_jail.rb
+++ b/config/initializers/safemode_jail.rb
@@ -1,15 +1,15 @@
# Permit safemode template rendering to have basic read-only access over
# model relations
class ActiveRecord::AssociationRelation::Jail < Safemode::Jail
- allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids
+ allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids, :sort_by, :select, :reject
end
class ActiveRecord::Relation::Jail < Safemode::Jail
- allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids
+ allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids, :select, :reject
end
class ActiveRecord::Associations::CollectionProxy::Jail < Safemode::Jail
- allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids
+ allow :[], :each, :first, :to_a, :map, :find_in_batches, :size, :group_by, :ids, :sort_by, :select, :reject
end
class ActiveRecord::Batches::BatchEnumerator::Jail < Safemode::Jail
@@ -23,3 +23,7 @@ class URI::Generic::Jail < Safemode::Jail
class ActiveSupport::TimeWithZone::Jail < Safemode::Jail
allow(*Safemode.core_jail_methods(Time).uniq)
end
+
+class Array::Jail < Safemode::Jail
+ allow :sort_by, :select, :reject
+end
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/finish/Kickstart_default_finish.host4dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/finish/Kickstart_default_finish.host4dhcp.snap.txt
index 1018307b2dc..89e5d2fa38a 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/finish/Kickstart_default_finish.host4dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/finish/Kickstart_default_finish.host4dhcp.snap.txt
@@ -3,10 +3,6 @@
-service network restart
-
-
-
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_SLES_default.host4dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_SLES_default.host4dhcp.snap.txt
index fc5785ad575..3625d8ac056 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_SLES_default.host4dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_SLES_default.host4dhcp.snap.txt
@@ -16,6 +16,12 @@
+
+ dhcp
+ eth0
+ no
+ auto
+
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_default.host4dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_default.host4dhcp.snap.txt
index 348e096ff33..aa794a3cfcd 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_default.host4dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/AutoYaST_default.host4dhcp.snap.txt
@@ -16,6 +16,12 @@
+
+ dhcp
+ eth0
+ no
+ auto
+
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4and6dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4and6dhcp.snap.txt
index 0231879d37c..1b8ae75652b 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4and6dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4and6dhcp.snap.txt
@@ -12,7 +12,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-6-dhcp-el7 --noipv6 --bootproto dhcp --mtu=1142 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-6-dhcp-el7 --noipv6 --mtu=1142 --bootproto dhcp --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -66,10 +66,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-6-dhcp-el7 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4dhcp.snap.txt
index d46358589a8..3a77ebe2d28 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4dhcp.snap.txt
@@ -12,7 +12,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-el7 --noipv6 --bootproto dhcp --mtu=1142 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-el7 --noipv6 --mtu=1142 --bootproto dhcp --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -66,10 +66,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-dhcp-el7 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4static.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4static.snap.txt
index b32d7c02779..a7a76b8f2e2 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4static.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host4static.snap.txt
@@ -12,7 +12,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-static-el7 --noipv6 --bootproto static --ip=192.168.42.42 --netmask=255.255.255.0 --gateway=192.168.42.1 --mtu=1242 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-static-el7 --noipv6 --mtu=1242 --bootproto static --ip=192.168.42.42 --netmask=255.255.255.0 --gateway=192.168.42.1 --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -66,10 +66,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-static-el7 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6dhcp.snap.txt
index eefa7a29ae1..faa259b4e0e 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6dhcp.snap.txt
@@ -12,7 +12,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv6-dhcp-el7 --noipv6 --bootproto dhcp --mtu=1342 --nameserver=2001:db8:42::8,2001:db8:42::4
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv6-dhcp-el7 --noipv6 --mtu=1342 --bootproto dhcp --nameserver=2001:db8:42::8,2001:db8:42::4
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -66,10 +66,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv6-dhcp-el7 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6static.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6static.snap.txt
index 670ce754d3f..b5f1e22d587 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6static.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.host6static.snap.txt
@@ -12,7 +12,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv6-static-el7 --noipv6 --bootproto static --ip=2001:db8:42::42 --netmask=ffff:ffff:ffff:: --gateway=2001:db8:42::1 --mtu=1442 --nameserver=2001:db8:42::8,2001:db8:42::4
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv6-static-el7 --noipv6 --mtu=1442 --bootproto static --ip=2001:db8:42::42 --netmask=ffff:ffff:ffff:: --gateway=2001:db8:42::1 --nameserver=2001:db8:42::8,2001:db8:42::4
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -66,10 +66,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv6-static-el7 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rhel9_dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rhel9_dhcp.snap.txt
index afcc93123d0..44bcd444c51 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rhel9_dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rhel9_dhcp.snap.txt
@@ -11,7 +11,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rhel9 --noipv6 --bootproto dhcp --mtu=1142 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rhel9 --noipv6 --mtu=1142 --bootproto dhcp --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -65,10 +65,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-dhcp-rhel9 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky8_dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky8_dhcp.snap.txt
index ef09090eae8..5fd2fdb986e 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky8_dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky8_dhcp.snap.txt
@@ -11,7 +11,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rocky8 --noipv6 --bootproto dhcp --mtu=1142 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rocky8 --noipv6 --mtu=1142 --bootproto dhcp --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -64,10 +64,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-dhcp-rocky8 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky9_dhcp.snap.txt b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky9_dhcp.snap.txt
index 12d06a466e3..365c0d597c3 100644
--- a/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky9_dhcp.snap.txt
+++ b/test/unit/foreman/renderer/snapshots/ProvisioningTemplate/provision/Kickstart_default.rocky9_dhcp.snap.txt
@@ -11,7 +11,7 @@ lang en_US.UTF-8
selinux --enforcing
keyboard us
-network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rocky9 --noipv6 --bootproto dhcp --mtu=1142 --nameserver=192.168.42.2,192.168.42.3
+network --device=00-f0-54-1a-7e-e0 --hostname snapshot-ipv4-dhcp-rocky9 --noipv6 --mtu=1142 --bootproto dhcp --nameserver=192.168.42.2,192.168.42.3
rootpw --iscrypted $1$rtd8Ub7R$5Ohzuy8WXlkaK9cA2T1wb0
firewall --service=ssh
@@ -63,10 +63,6 @@ chvt 3
(
logger "Starting anaconda snapshot-ipv4-dhcp-rocky9 postinstall"
-
-
-
-
echo "Updating system time"
systemctl enable --now chronyd
/usr/bin/chronyc -a makestep
diff --git a/test/unit/foreman/templates/snippets/kickstart_network_interface_test.rb b/test/unit/foreman/templates/snippets/kickstart_network_interface_test.rb
new file mode 100644
index 00000000000..a6fb328622e
--- /dev/null
+++ b/test/unit/foreman/templates/snippets/kickstart_network_interface_test.rb
@@ -0,0 +1,354 @@
+require 'test_helper'
+
+class KickstartNetworkInterfaceTest < ActiveSupport::TestCase
+ def renderer
+ @renderer ||= Foreman::Renderer::SafeModeRenderer
+ end
+
+ def render_template(iface, host:, use_slaac:, static:, static6:)
+ @snippet ||= File.read(Rails.root.join('app', 'views', 'unattended', 'provisioning_templates', 'snippet', 'kickstart_network_interface.erb'))
+
+ source = OpenStruct.new(
+ name: 'Test',
+ content: @snippet
+ )
+
+ scope = Class.new(Foreman::Renderer::Scope::Provisioning).send(
+ :new,
+ host: host,
+ source: source,
+ variables: {
+ iface: iface,
+ host: host,
+ use_slaac: use_slaac,
+ static: static,
+ static6: static6,
+ })
+
+ renderer.render(source, scope)
+ end
+
+ setup do
+ os = FactoryBot.create(:for_snapshots_rhel9, :with_provision, :with_associations)
+
+ @host = FactoryBot.create(:host, :managed, :build => true, :operatingsystem => os,
+ :interfaces => [
+ FactoryBot.build(:nic_managed, :primary => true),
+ FactoryBot.build(:nic_managed, :provision => true),
+ ])
+ end
+
+ describe '#network' do
+ test 'should return a network line for an interface' do
+ actual = render_template(
+ @host.managed_interfaces.first,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_match(/network/, actual)
+ end
+
+ test 'should skip non-managed interfaces' do
+ iface = FactoryBot.build(:nic_base, primary: true, managed: false)
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_empty actual
+ end
+
+ test 'should create bond interface' do
+ iface = FactoryBot.build(
+ :nic_bond,
+ primary: true,
+ identifier: 'test_bond',
+ attached_devices: ['bonded_slave1', 'bonded_slave2'],
+ mode: 'test_mode',
+ bond_options: 'option_a=foo option_b=bar'
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(bondslaves_match = /--bondslaves=([^ ]*)/.match(actual))
+ assert_match(/bonded_slave1/, bondslaves_match[1])
+ assert_match(/bonded_slave2/, bondslaves_match[1])
+ assert_not_nil(bondopts_match = /--bondopts=([^ ]*)/.match(actual))
+ assert_match(/mode=test_mode,/, bondopts_match[1])
+ assert_match(/,option_a=foo,option_b=bar/, bondopts_match[1])
+ end
+
+ test 'should create bridge interface' do
+ iface = FactoryBot.build(
+ :nic_bridge,
+ primary: true,
+ identifier: 'test_bridge',
+ attached_devices: ['bridged_slave1', 'bridged_slave2'],
+ attrs: {bridge: true}
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(bridgeslaves_match = /--bridgeslaves=([^ ]*)/.match(actual))
+ assert_match(/bridged_slave1/, bridgeslaves_match[1])
+ assert_match(/bridged_slave2/, bridgeslaves_match[1])
+ end
+
+ test 'should set correct noipv6 flag' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4)
+ )
+
+ iface.subnet6 = nil
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_match(/--noipv6/, actual)
+ end
+
+ test 'should set correct noipv4 flag' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet6: FactoryBot.build(:subnet_ipv6)
+ )
+
+ iface.subnet = nil
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_match(/--noipv4/, actual)
+ end
+
+ test 'should use static ipv4 configuration' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4_static_for_snapshots)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: true,
+ static6: false
+ )
+
+ assert_match(/--ip/, actual)
+ assert_match(/--netmask/, actual)
+ assert_match(/--gateway/, actual)
+ assert_not_nil(bootproto_match = /--bootproto ([^ ]*)/.match(actual))
+ assert_match(/static/, bootproto_match[1])
+ end
+
+ test 'should use dhcp ipv4 configuration' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4_with_domains)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(bootproto_match = /--bootproto ([^ ]*)/.match(actual))
+ assert_match(/dhcp/, bootproto_match[1])
+ end
+
+ test 'should use static ipv6 configuration' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet6: FactoryBot.build(:subnet_ipv6_static_for_snapshots),
+ ip6: '2001:db8:42::2'
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: true
+ )
+
+ assert_not_nil(ipv6_match = %r{--ipv6=([^/]*)/([^ ]*)}.match(actual))
+ assert_match(iface.ip6, ipv6_match[1])
+ assert_match(iface.subnet6.cidr.to_s, ipv6_match[2])
+ assert_not_nil(gateway_match = /--ipv6gateway=([^ ]*)/.match(actual))
+ assert_match(iface.subnet6.gateway, gateway_match[1])
+ end
+
+ test 'should use dhcp ipv6 configuration' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet6: FactoryBot.build(:subnet_ipv6_with_domains)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(ipv6_match = /--ipv6 ([^ ]*)/.match(actual))
+ assert_match(/dhcp/, ipv6_match[1])
+ end
+
+ test 'should use auto ipv6 configuration' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet6: FactoryBot.build(:subnet_ipv6_dhcp_for_snapshots)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: true,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(ipv6_match = /--ipv6 ([^ ]*)/.match(actual))
+ assert_match(/auto/, ipv6_match[1])
+ end
+
+ test 'should set vlan options' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ virtual: true,
+ tag: '333',
+ attached_to: 'test_iface1'
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(vlan_match = /--vlanid=([^ ]*)/.match(actual))
+ assert_match(/333/, vlan_match[1])
+ assert_not_nil(interfacename_match = /--interfacename=([^ ]*)/.match(actual))
+ assert_match(/vlan333/, interfacename_match[1])
+ end
+
+ test 'should set DNS servers' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4_static_for_snapshots)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(nameserver_match = /--nameserver=([^ ]*)/.match(actual))
+ # order is not promised for nameserver list
+ assert_match(/192.168.42.2/, nameserver_match[1])
+ assert_match(/192.168.42.3/, nameserver_match[1])
+ end
+
+ test 'should set nodns flag' do
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4)
+ )
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_match(/--nodns/, actual)
+ end
+
+ test 'should set search domain' do
+ os = FactoryBot.create(
+ :for_snapshots_rhel9,
+ :with_provision,
+ :with_associations,
+ name: 'RHEL',
+ major: '10',
+ minor: '0',
+ type: 'Redhat',
+ title: 'Red Hat Enterprise Linux 10.0'
+ )
+
+ @host.operatingsystem = os
+
+ iface = FactoryBot.build(
+ :nic_managed,
+ primary: true,
+ subnet: FactoryBot.build(:subnet_ipv4)
+ )
+
+ iface.domain = FactoryBot.build(:domain, name: 'test.com')
+
+ actual = render_template(
+ iface,
+ host: @host,
+ use_slaac: false,
+ static: false,
+ static6: false
+ )
+
+ assert_not_nil(dns_search_match = /--ipv4-dns-search=([^ ]*)/.match(actual))
+ assert_match(/test.com/, dns_search_match[1])
+ end
+ end
+end