From 9fdcb20ce706a0da0716a1b17347bd5257255d58 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 14 Jul 2022 15:33:47 +0200 Subject: [PATCH 1/2] Enable boot image download for iso images * Implement fetch and extract boot image * Apply correct file permissions * Introduce and add tests for fetch_boot_image * Implement class for file extraction --- lib/proxy/archive_extract.rb | 21 +++++++++++++++++++++ lib/smart_proxy_main.rb | 1 + modules/tftp/server.rb | 15 +++++++++++++++ modules/tftp/tftp_api.rb | 4 ++++ test/tftp/tftp_api_test.rb | 8 +++++++- test/tftp/tftp_test.rb | 3 ++- 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 lib/proxy/archive_extract.rb diff --git a/lib/proxy/archive_extract.rb b/lib/proxy/archive_extract.rb new file mode 100644 index 000000000..baae09735 --- /dev/null +++ b/lib/proxy/archive_extract.rb @@ -0,0 +1,21 @@ +module Proxy + class ArchiveExtract < Proxy::Util::CommandTask + include Util + + def initialize(src, dst, skip_existing = true) + + args = [which('7z')] + + # extract command + args << "x" + # source file + args << src.to_s + # skip existing files + args << "-aos" if skip_existing + # destination directory + args << "-o#{dst}" + + super(args) + end + end +end diff --git a/lib/smart_proxy_main.rb b/lib/smart_proxy_main.rb index 0ebc22b2c..cb1217cf4 100644 --- a/lib/smart_proxy_main.rb +++ b/lib/smart_proxy_main.rb @@ -14,6 +14,7 @@ require 'proxy/dependency_injection' require 'proxy/util' require 'proxy/http_download' +require 'proxy/archive_extract' require 'proxy/helpers' require 'proxy/memory_store' require 'proxy/plugin_validators' diff --git a/modules/tftp/server.rb b/modules/tftp/server.rb index eba4e5e4a..a377cfcb0 100644 --- a/modules/tftp/server.rb +++ b/modules/tftp/server.rb @@ -150,6 +150,21 @@ def pxeconfig_file(mac) end end + def self.fetch_boot_image(dst, src) + + # Verify dst is a valid directory + dst_file = Pathname.new(dst).cleanpath + dst_path = Pathname.new(dst.delete_suffix(".iso")) + + FileUtils.mkdir_p dst_path.parent + choose_protocol_and_fetch(src, dst_file).join + # extract iso + extract_task = ::Proxy::ArchiveExtract.new(dst_file, dst_path).start + raise "TFTP image extraction error" unless extract_task.join == 0 + # adapt extracted file permission + FileUtils.chmod_R 0755, dst_path + end + def self.fetch_boot_file(dst, src) filename = boot_filename(dst, src) destination = Pathname.new(File.expand_path(filename, Proxy::TFTP::Plugin.settings.tftproot)).cleanpath diff --git a/modules/tftp/tftp_api.rb b/modules/tftp/tftp_api.rb index 1bc6276c7..6ad0bd44c 100644 --- a/modules/tftp/tftp_api.rb +++ b/modules/tftp/tftp_api.rb @@ -34,6 +34,10 @@ def create_default(variant) end end + post "/fetch_boot_image" do + log_halt(400, "TFTP: Failed to fetch boot file: ") { Proxy::TFTP.fetch_boot_image(params[:path], params[:url]) } + end + post "/fetch_boot_file" do log_halt(400, "TFTP: Failed to fetch boot file: ") { Proxy::TFTP.fetch_boot_file(params[:prefix], params[:path]) } end diff --git a/test/tftp/tftp_api_test.rb b/test/tftp/tftp_api_test.rb index 3ffcfb0cb..9e9270ada 100644 --- a/test/tftp/tftp_api_test.rb +++ b/test/tftp/tftp_api_test.rb @@ -111,7 +111,13 @@ def test_api_can_fetch_boot_file assert last_response.ok? end - def test_api_can_get_servername + def test_api_can_fetch_boot_image + Proxy::TFTP.expects(:fetch_boot_image).with('some/image.iso', 'http://localhost/file.iso').returns(true) + post "/fetch_boot_image", :path => 'some/image.iso', :url => 'http://localhost/file.iso' + assert last_response.ok? + end + + def test_api_can_get_servername Proxy::TFTP::Plugin.settings.stubs(:tftp_servername).returns("servername") result = get "/serverName" assert_match /servername/, result.body diff --git a/test/tftp/tftp_test.rb b/test/tftp/tftp_test.rb index 11dc6e204..874eb68e8 100644 --- a/test/tftp/tftp_test.rb +++ b/test/tftp/tftp_test.rb @@ -5,7 +5,8 @@ class TftpTest < Test::Unit::TestCase def setup @tftp = Proxy::TFTP::Server.new - Proxy::TFTP::Plugin.load_test_settings(:tftproot => "/some/root") + Proxy::TFTP::Plugin.load_test_settings(:tftproot => "/some/root", + :tftp_image_path => "/another/root") end def test_should_have_a_logger From 40b558212e860571bb46ad69ee5a174d00a3f7eb Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Wed, 27 Jul 2022 17:09:17 +0200 Subject: [PATCH 2/2] Prototype: replace 7z with isoinfo --- lib/proxy/archive_extract.rb | 26 ++++++++++++++++---------- lib/proxy/util.rb | 6 ++++++ modules/tftp/server.rb | 29 +++++++++++++++++++---------- modules/tftp/tftp_api.rb | 2 +- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/lib/proxy/archive_extract.rb b/lib/proxy/archive_extract.rb index baae09735..02244036f 100644 --- a/lib/proxy/archive_extract.rb +++ b/lib/proxy/archive_extract.rb @@ -2,20 +2,26 @@ module Proxy class ArchiveExtract < Proxy::Util::CommandTask include Util - def initialize(src, dst, skip_existing = true) + def initialize(image_path, file_in_image, dst_path) - args = [which('7z')] + args = [which('isoinfo')] - # extract command - args << "x" - # source file - args << src.to_s - # skip existing files - args << "-aos" if skip_existing - # destination directory - args << "-o#{dst}" + # read the file + args << "-R" + # set image path + args += ["-i", image_path.to_s] + # set file path within the image + args += ["-x", file_in_image.to_s] + # save destination path + @dst_path = dst_path super(args) end + + def start + super do + File.open(@dst_path, "w+") { |file| file.write(@output) } + end + end end end diff --git a/lib/proxy/util.rb b/lib/proxy/util.rb index e191a70dd..7966af303 100644 --- a/lib/proxy/util.rb +++ b/lib/proxy/util.rb @@ -16,6 +16,7 @@ class CommandTask def initialize(command, input = nil) @command = command @input = input + @output = [] end def start(&ensured_block) @@ -29,6 +30,7 @@ def start(&ensured_block) stdin.close stdout.each do |line| logger.debug "[#{thr.pid}] #{line}" + @output.append(line) end stderr.each do |line| logger.warn "[#{thr.pid}] #{line}" @@ -47,6 +49,10 @@ def start(&ensured_block) def join @task.value end + + def output + @output + end end # convert setting to boolean (with a default value) diff --git a/modules/tftp/server.rb b/modules/tftp/server.rb index a377cfcb0..3d119dde6 100644 --- a/modules/tftp/server.rb +++ b/modules/tftp/server.rb @@ -150,19 +150,28 @@ def pxeconfig_file(mac) end end - def self.fetch_boot_image(dst, src) + def self.fetch_boot_image(image_dst, url, files) # Verify dst is a valid directory - dst_file = Pathname.new(dst).cleanpath - dst_path = Pathname.new(dst.delete_suffix(".iso")) - - FileUtils.mkdir_p dst_path.parent - choose_protocol_and_fetch(src, dst_file).join - # extract iso - extract_task = ::Proxy::ArchiveExtract.new(dst_file, dst_path).start - raise "TFTP image extraction error" unless extract_task.join == 0 + image_path = Pathname.new(image_dst).cleanpath + extr_image_dir = Pathname.new(image_dst.delete_suffix(".iso")) + + FileUtils.mkdir_p image_path.parent + choose_protocol_and_fetch(url, image_path).join + + files.each do |file| + file_path = Pathname.new file + extr_file_path = Pathname.new(File.join(extr_image_dir, file_path)).cleanpath + + # Create destination directory + FileUtils.mkdir_p extr_file_path.parent + # extract iso + extract_task = ::Proxy::ArchiveExtract.new(image_path, file_path, extr_file_path).start + raise "TFTP image file extraction error: #{file_path}" unless extract_task.join == 0 + end + # adapt extracted file permission - FileUtils.chmod_R 0755, dst_path + FileUtils.chmod_R 0755, extr_image_dir end def self.fetch_boot_file(dst, src) diff --git a/modules/tftp/tftp_api.rb b/modules/tftp/tftp_api.rb index 6ad0bd44c..4f8f2c4da 100644 --- a/modules/tftp/tftp_api.rb +++ b/modules/tftp/tftp_api.rb @@ -35,7 +35,7 @@ def create_default(variant) end post "/fetch_boot_image" do - log_halt(400, "TFTP: Failed to fetch boot file: ") { Proxy::TFTP.fetch_boot_image(params[:path], params[:url]) } + log_halt(400, "TFTP: Failed to fetch boot file: ") { Proxy::TFTP.fetch_boot_image(params[:path], params[:url], params[:files]) } end post "/fetch_boot_file" do