diff --git a/service/lib/agama/network.rb b/service/lib/agama/network.rb index 972ec5162f..4a7b0053b8 100644 --- a/service/lib/agama/network.rb +++ b/service/lib/agama/network.rb @@ -46,11 +46,33 @@ def install ProxySetup.instance.install end + def link_resolv + return unless File.exist?(RESOLV) + + link = File.join(Yast::Installation.destdir, RESOLV) + target = File.join(RUN_NM_DIR, File.basename(RESOLV)) + + return if File.exist?(link) + + FileUtils.touch RESOLV_FLAG + FileUtils.ln_s target, link + end + + def unlink_resolv + return unless File.exist?(RESOLV_FLAG) + + link = File.join(Yast::Installation.destdir, RESOLV) + FileUtils.rm_f link + FileUtils.rm_f RESOLV_FLAG + end + private # @return [Logger] attr_reader :logger + RESOLV = "/etc/resolv.conf" + RESOLV_FLAG = "/run/agama/manage_resolv" ETC_NM_DIR = "/etc/NetworkManager" RUN_NM_DIR = "/run/NetworkManager" private_constant :ETC_NM_DIR diff --git a/service/lib/agama/storage/finisher.rb b/service/lib/agama/storage/finisher.rb index e04e42fc95..13051aead7 100644 --- a/service/lib/agama/storage/finisher.rb +++ b/service/lib/agama/storage/finisher.rb @@ -27,6 +27,7 @@ require "agama/with_progress" require "agama/helpers" require "agama/http" +require "agama/network" require "abstract_method" require "fileutils" @@ -261,9 +262,15 @@ def run # Run the post scripts def run_post_scripts - require "agama/http" + network.link_resolv client = Agama::HTTP::Clients::Scripts.new client.run("post") + ensure + network.unlink_resolv + end + + def network + @network ||= Agama::Network.new(logger) end # Enables the agama-scripts service to run init scripts diff --git a/service/package/rubygem-agama-yast.changes b/service/package/rubygem-agama-yast.changes index 1232f89b92..0d3897badd 100644 --- a/service/package/rubygem-agama-yast.changes +++ b/service/package/rubygem-agama-yast.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Mar 20 08:41:23 UTC 2025 - Knut Anderssen + +- Symlink the /mnt/etc/resolv.conf to the NetworkManager running + one in order to have DNS resolution in the chroot post scripts + (bsc#1235617, gh#agama-project/agama#2179). + ------------------------------------------------------------------- Fri Mar 14 12:34:03 UTC 2025 - Imobach Gonzalez Sosa diff --git a/service/test/agama/network_test.rb b/service/test/agama/network_test.rb index d67e12a7d1..eee39498ac 100644 --- a/service/test/agama/network_test.rb +++ b/service/test/agama/network_test.rb @@ -28,25 +28,29 @@ subject(:network) { described_class.new(logger) } let(:logger) { Logger.new($stdout, level: :warn) } + let(:targetdir) { File.join(rootdir, "mnt") } + + before do + allow(Yast::Installation).to receive(:destdir).and_return(targetdir) + end + + after do + FileUtils.remove_entry(rootdir) + end describe "#install" do let(:rootdir) { Dir.mktmpdir } + let(:etcdir) do File.join(rootdir, "etc", "NetworkManager", "system-connections") end - let(:targetdir) { File.join(rootdir, "mnt") } let(:service) { instance_double(Yast2::Systemd::Service, enable: nil) } before do - allow(Yast::Installation).to receive(:destdir).and_return(targetdir) allow(Yast2::Systemd::Service).to receive(:find).with("NetworkManager").and_return(service) stub_const("Agama::Network::ETC_NM_DIR", etcdir) end - after do - FileUtils.remove_entry(rootdir) - end - context "when NetworkManager configuration files are present" do before do FileUtils.mkdir_p(File.join(etcdir, "system-connections")) @@ -95,4 +99,74 @@ end end end + + describe "#link_resolv" do + let(:rootdir) { Dir.mktmpdir } + + let(:fixtures) { File.join(FIXTURES_PATH, "root_dir") } + let(:resolv_fixture) { File.join(FIXTURES_PATH, "etc", "resolv.conf") } + let(:resolv_flag) { File.join(rootdir, "run", "agama", "manage_resolv") } + let(:resolv) { File.join(targetdir, "etc", "resolv.conf") } + + before do + stub_const("Agama::Network::RESOLV_FLAG", resolv_flag) + stub_const("Agama::Network::RUN_NM_DIR", File.join(rootdir, "run", "NetworkManager")) + FileUtils.mkdir_p targetdir + FileUtils.cp_r(Dir["#{fixtures}/*"], rootdir) + FileUtils.cp_r(Dir["#{fixtures}/*"], targetdir) + end + + context "when the /etc/resolv.conf exists in the installation destdir" do + before do + FileUtils.mkdir_p File.join(targetdir, "etc") + FileUtils.touch File.join(targetdir, "etc", "resolv.conf") + end + + it "does nothing" do + expect(FileUtils).to_not receive(:ln_s) + network.link_resolv + end + end + + context "when there is no /etc/resolv.conf in the installation destdir" do + it "symlinks it to /run/NetworkManager/resolv.conf" do + network.link_resolv + expect(File.exist?(resolv)).to eql(true) + expect(File.symlink?(resolv)).to eql(true) + end + + it "creates a flag indicating that the resolv.conf is managed by Agama" do + network.link_resolv + expect(File.exist?(resolv_flag)).to eql(true) + end + end + end + + describe "#unlink_resolv" do + let(:rootdir) { Dir.mktmpdir } + + let(:fixtures) { File.join(FIXTURES_PATH, "root_dir") } + let(:resolv_fixture) { File.join(FIXTURES_PATH, "etc", "resolv.conf") } + let(:resolv_flag) { File.join(rootdir, "run", "agama", "manage_resolv") } + let(:resolv) { File.join(targetdir, "etc", "resolv.conf") } + + before do + stub_const("Agama::Network::RESOLV_FLAG", resolv_flag) + stub_const("Agama::Network::RUN_NM_DIR", File.join(rootdir, "run", "NetworkManager")) + FileUtils.mkdir_p targetdir + FileUtils.cp_r(Dir["#{fixtures}/*"], rootdir) + FileUtils.cp_r(Dir["#{fixtures}/*"], targetdir) + end + + context "when the /etc/resolv.conf was marked as managed by Agama" do + it "removes the /etc/resolv.con symlink from the installation destdir" do + network.link_resolv + expect(File.exist?(resolv_flag)).to eql(true) + expect(File.symlink?(resolv)).to eql(true) + network.unlink_resolv + expect(File.exist?(resolv)).to eql(false) + expect(File.exist?(resolv_flag)).to eql(false) + end + end + end end diff --git a/service/test/agama/storage/manager_test.rb b/service/test/agama/storage/manager_test.rb index a278f6a314..89772044e5 100644 --- a/service/test/agama/storage/manager_test.rb +++ b/service/test/agama/storage/manager_test.rb @@ -22,7 +22,7 @@ require_relative "../../test_helper" require_relative "../with_progress_examples" require_relative "../with_issues_examples" -require_relative "./storage_helpers" +require_relative "storage_helpers" require "agama/dbus/clients/questions" require "agama/config" require "agama/http" @@ -59,6 +59,7 @@ # mock writting config as proposal call can do storage probing, which fails in CI allow_any_instance_of(Agama::Storage::Bootloader).to receive(:write_config) allow(Agama::HTTP::Clients::Scripts).to receive(:new).and_return(scripts_client) + allow(Agama::Network).to receive(:new).and_return(network) allow(Yast::Installation).to receive(:destdir).and_return(File.join(tmp_dir, "mnt")) stub_const("Agama::Storage::Finisher::CopyLogsStep::SCRIPTS_DIR", File.join(tmp_dir, "run", "agama", "scripts")) @@ -73,6 +74,7 @@ let(:software) do instance_double(Agama::DBus::Clients::Software, selected_product: "ALP") end + let(:network) { instance_double(Agama::Network, link_resolv: nil, unlink_resolv: nil) } let(:bootloader_finish) { instance_double(Bootloader::FinishClient, write: nil) } let(:security) { instance_double(Agama::Security, probe: nil, write: nil) } @@ -378,13 +380,16 @@ let(:devicegraph) { "staging-plain-partitions.yaml" } it "copy needed files, installs the bootloader, sets up the snapshots, " \ - "copy logs, runs the post-installation scripts, and umounts the file systems" do + "copy logs, symlink resolv.conf, runs the post-installation scripts, " \ + "unlink resolv.conf, and umounts the file systems" do expect(security).to receive(:write) expect(copy_files).to receive(:run) expect(bootloader_finish).to receive(:write) expect(Yast::WFM).to receive(:CallFunction).with("storage_finish", ["Write"]) expect(Yast::WFM).to receive(:CallFunction).with("snapshots_finish", ["Write"]) + expect(network).to receive(:link_resolv) expect(scripts_client).to receive(:run).with("post") + expect(network).to receive(:unlink_resolv) expect(Yast::Execute).to receive(:on_target!) .with("systemctl", "enable", "agama-scripts", allowed_exitstatus: [0, 1]) expect(Yast::WFM).to receive(:CallFunction).with("umount_finish", ["Write"]) diff --git a/service/test/fixtures/root_dir/run/NetworkManager/resolv.conf b/service/test/fixtures/root_dir/run/NetworkManager/resolv.conf new file mode 100644 index 0000000000..04aa3d9f6d --- /dev/null +++ b/service/test/fixtures/root_dir/run/NetworkManager/resolv.conf @@ -0,0 +1,3 @@ +# Generated by NetworkManager +search default +nameserver 192.168.100.1