From 50d995f51db2456703060de1cfe161c45a5fc852 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 29 Jul 2020 11:32:46 -0400 Subject: [PATCH 1/2] Guest support for OpenWrt This commit includes the following capabilties for OpenWrt: - Guest detection - SSH key replacement - Change host name - Rsync --- .../guests/openwrt/cap/change_host_name.rb | 19 +++++ plugins/guests/openwrt/cap/halt.rb | 16 ++++ .../guests/openwrt/cap/insert_public_key.rb | 20 +++++ .../guests/openwrt/cap/remove_public_key.rb | 22 ++++++ plugins/guests/openwrt/cap/rsync.rb | 35 +++++++++ plugins/guests/openwrt/guest.rb | 10 +++ plugins/guests/openwrt/plugin.rb | 61 +++++++++++++++ .../openwrt/cap/change_host_name_test.rb | 33 ++++++++ .../plugins/guests/openwrt/cap/halt_test.rb | 37 +++++++++ .../openwrt/cap/insert_public_key_test.rb | 30 ++++++++ .../openwrt/cap/remove_public_key_test.rb | 31 ++++++++ .../plugins/guests/openwrt/cap/rsync_test.rb | 76 +++++++++++++++++++ 12 files changed, 390 insertions(+) create mode 100644 plugins/guests/openwrt/cap/change_host_name.rb create mode 100644 plugins/guests/openwrt/cap/halt.rb create mode 100644 plugins/guests/openwrt/cap/insert_public_key.rb create mode 100644 plugins/guests/openwrt/cap/remove_public_key.rb create mode 100644 plugins/guests/openwrt/cap/rsync.rb create mode 100644 plugins/guests/openwrt/guest.rb create mode 100644 plugins/guests/openwrt/plugin.rb create mode 100644 test/unit/plugins/guests/openwrt/cap/change_host_name_test.rb create mode 100644 test/unit/plugins/guests/openwrt/cap/halt_test.rb create mode 100644 test/unit/plugins/guests/openwrt/cap/insert_public_key_test.rb create mode 100644 test/unit/plugins/guests/openwrt/cap/remove_public_key_test.rb create mode 100644 test/unit/plugins/guests/openwrt/cap/rsync_test.rb diff --git a/plugins/guests/openwrt/cap/change_host_name.rb b/plugins/guests/openwrt/cap/change_host_name.rb new file mode 100644 index 000000000..c26274b74 --- /dev/null +++ b/plugins/guests/openwrt/cap/change_host_name.rb @@ -0,0 +1,19 @@ +module VagrantPlugins + module GuestOpenWrt + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + comm = machine.communicate + + if !comm.test("uci get system.@system[0].hostname | grep '^#{name}$'", sudo: false) + comm.execute <<~EOH + uci set system.@system[0].hostname='#{name}' + uci commit system + /etc/init.d/system reload + EOH + end + end + end + end + end +end diff --git a/plugins/guests/openwrt/cap/halt.rb b/plugins/guests/openwrt/cap/halt.rb new file mode 100644 index 000000000..89cfdd16f --- /dev/null +++ b/plugins/guests/openwrt/cap/halt.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module GuestOpenWrt + module Cap + class Halt + def self.halt(machine) + begin + machine.communicate.execute("halt") + rescue IOError, Vagrant::Errors::SSHDisconnected + # Ignore, this probably means connection closed because it + # shut down. + end + end + end + end + end +end diff --git a/plugins/guests/openwrt/cap/insert_public_key.rb b/plugins/guests/openwrt/cap/insert_public_key.rb new file mode 100644 index 000000000..6abbf4504 --- /dev/null +++ b/plugins/guests/openwrt/cap/insert_public_key.rb @@ -0,0 +1,20 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestOpenWrt + module Cap + class InsertPublicKey + def self.insert_public_key(machine, contents) + contents = contents.chomp + contents = Vagrant::Util::ShellQuote.escape(contents, "'") + + machine.communicate.tap do |comm| + comm.execute <<~EOH + printf '#{contents}\\n' >> /etc/dropbear/authorized_keys + EOH + end + end + end + end + end +end diff --git a/plugins/guests/openwrt/cap/remove_public_key.rb b/plugins/guests/openwrt/cap/remove_public_key.rb new file mode 100644 index 000000000..21981181a --- /dev/null +++ b/plugins/guests/openwrt/cap/remove_public_key.rb @@ -0,0 +1,22 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module GuestOpenWrt + module Cap + class RemovePublicKey + def self.remove_public_key(machine, contents) + contents = contents.chomp + contents = Vagrant::Util::ShellQuote.escape(contents, "'") + + machine.communicate.tap do |comm| + comm.execute <<~EOH + if test -f /etc/dropbear/authorized_keys ; then + sed -i '/^.*#{contents}.*$/d' /etc/dropbear/authorized_keys + fi + EOH + end + end + end + end + end +end diff --git a/plugins/guests/openwrt/cap/rsync.rb b/plugins/guests/openwrt/cap/rsync.rb new file mode 100644 index 000000000..a81dc053b --- /dev/null +++ b/plugins/guests/openwrt/cap/rsync.rb @@ -0,0 +1,35 @@ +module VagrantPlugins + module GuestOpenWrt + module Cap + class RSync + def self.rsync_installed(machine) + machine.communicate.test("which rsync") + end + + def self.rsync_install(machine) + machine.communicate.tap do |comm| + comm.execute <<~EOH + opkg update + opkg install rsync + EOH + end + end + + def self.rsync_pre(machine, opts) + machine.communicate.tap do |comm| + comm.execute("mkdir -p '#{opts[:guestpath]}'") + end + end + + def self.rsync_command(machine) + "rsync -zz" + end + + def self.rsync_post(machine, opts) + # Don't do anything because BusyBox's `find` doesn't support the + # syntax in plugins/synced_folders/rsync/default_unix_cap.rb. + end + end + end + end +end diff --git a/plugins/guests/openwrt/guest.rb b/plugins/guests/openwrt/guest.rb new file mode 100644 index 000000000..55f871b6d --- /dev/null +++ b/plugins/guests/openwrt/guest.rb @@ -0,0 +1,10 @@ +require_relative '../linux/guest' + +module VagrantPlugins + module GuestOpenWrt + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "openwrt".freeze + end + end +end diff --git a/plugins/guests/openwrt/plugin.rb b/plugins/guests/openwrt/plugin.rb new file mode 100644 index 000000000..0d8eee481 --- /dev/null +++ b/plugins/guests/openwrt/plugin.rb @@ -0,0 +1,61 @@ +require "vagrant" + +module VagrantPlugins + module GuestOpenWrt + class Plugin < Vagrant.plugin("2") + name "OpenWrt guest" + description "OpenWrt guest support." + + guest(:openwrt, :linux) do + require_relative "guest" + Guest + end + + guest_capability(:openwrt, :insert_public_key) do + require_relative "cap/insert_public_key" + Cap::InsertPublicKey + end + + guest_capability(:openwrt, :remove_public_key) do + require_relative "cap/remove_public_key" + Cap::RemovePublicKey + end + + guest_capability(:openwrt, :change_host_name) do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + + guest_capability(:openwrt, :rsync_installed) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:openwrt, :rsync_install) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:openwrt, :rsync_pre) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:openwrt, :rsync_command) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:openwrt, :rsync_post) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:openwrt, :halt) do + require_relative "cap/halt" + Cap::Halt + end + end + end +end + diff --git a/test/unit/plugins/guests/openwrt/cap/change_host_name_test.rb b/test/unit/plugins/guests/openwrt/cap/change_host_name_test.rb new file mode 100644 index 000000000..2557fcdea --- /dev/null +++ b/test/unit/plugins/guests/openwrt/cap/change_host_name_test.rb @@ -0,0 +1,33 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestOpenWrt::Cap::ChangeHostName" do + let(:caps) do + VagrantPlugins::GuestOpenWrt::Plugin + .components + .guest_capabilities[:openwrt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".change_host_name" do + let(:cap) { caps.get(:change_host_name) } + + it "changes the hostname if appropriate" do + cap.change_host_name(machine, "testhost") + + expect(comm.received_commands[0]).to match(/uci get system\.@system\[0\].hostname | grep '^testhost$'/) + expect(comm.received_commands[1]).to match(/uci set system.@system\[0\].hostname='testhost'/) + expect(comm.received_commands[1]).to match(/uci commit system/) + expect(comm.received_commands[1]).to match(/\/etc\/init.d\/system reload/) + end + end +end diff --git a/test/unit/plugins/guests/openwrt/cap/halt_test.rb b/test/unit/plugins/guests/openwrt/cap/halt_test.rb new file mode 100644 index 000000000..af423dc84 --- /dev/null +++ b/test/unit/plugins/guests/openwrt/cap/halt_test.rb @@ -0,0 +1,37 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestOpenWrt::Cap::Halt" do + let(:plugin) { VagrantPlugins::GuestOpenWrt::Plugin.components.guest_capabilities[:openwrt].get(:halt) } + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:shutdown_command){ "halt" } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + end + + after do + communicator.verify_expectations! + end + + describe ".halt" do + it "sends a shutdown signal" do + communicator.expect_command(shutdown_command) + plugin.halt(machine) + end + + it "ignores an IOError" do + communicator.stub_command(shutdown_command, raise: IOError) + expect { + plugin.halt(machine) + }.to_not raise_error + end + + it "ignores a Vagrant::Errors::SSHDisconnected" do + communicator.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected) + expect { + plugin.halt(machine) + }.to_not raise_error + end + end +end diff --git a/test/unit/plugins/guests/openwrt/cap/insert_public_key_test.rb b/test/unit/plugins/guests/openwrt/cap/insert_public_key_test.rb new file mode 100644 index 000000000..0daa96e6a --- /dev/null +++ b/test/unit/plugins/guests/openwrt/cap/insert_public_key_test.rb @@ -0,0 +1,30 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestOpenWrt::Cap::InsertPublicKey" do + let(:caps) do + VagrantPlugins::GuestOpenWrt::Plugin + .components + .guest_capabilities[:openwrt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".insert_public_key" do + let(:cap) { caps.get(:insert_public_key) } + + it "inserts the public key" do + cap.insert_public_key(machine, "ssh-rsa ...") + + expect(comm.received_commands[0]).to match(/printf 'ssh-rsa ...\\n' >> \/etc\/dropbear\/authorized_keys/) + end + end +end diff --git a/test/unit/plugins/guests/openwrt/cap/remove_public_key_test.rb b/test/unit/plugins/guests/openwrt/cap/remove_public_key_test.rb new file mode 100644 index 000000000..c4bc71ca4 --- /dev/null +++ b/test/unit/plugins/guests/openwrt/cap/remove_public_key_test.rb @@ -0,0 +1,31 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestOpenWrt::Cap::RemovePublicKey" do + let(:caps) do + VagrantPlugins::GuestOpenWrt::Plugin + .components + .guest_capabilities[:openwrt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".remove_public_key" do + let(:cap) { caps.get(:remove_public_key) } + + it "removes the public key" do + cap.remove_public_key(machine, "ssh-rsa keyvalue comment") + expect(comm.received_commands[0]).to match(/if test -f \/etc\/dropbear\/authorized_keys ; then/) + expect(comm.received_commands[0]).to match(/sed -i '\/\^.*ssh-rsa keyvalue comment.*\$\/d' \/etc\/dropbear\/authorized_keys/) + expect(comm.received_commands[0]).to match(/fi/) + end + end +end diff --git a/test/unit/plugins/guests/openwrt/cap/rsync_test.rb b/test/unit/plugins/guests/openwrt/cap/rsync_test.rb new file mode 100644 index 000000000..8d17cc517 --- /dev/null +++ b/test/unit/plugins/guests/openwrt/cap/rsync_test.rb @@ -0,0 +1,76 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::VagrantPlugins::Cap::Rsync" do + let(:caps) do + VagrantPlugins::GuestOpenWrt::Plugin + .components + .guest_capabilities[:openwrt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:guest_directory) { "/guest/directory/path" } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".rsync_installed" do + let(:cap) { caps.get(:rsync_installed) } + + describe "when rsync is in the path" do + it "is true" do + comm.stub_command("which rsync", stdout: '/usr/bin/rsync', exit_code: 0) + expect(cap.rsync_installed(machine)).to be true + end + end + + describe "when rsync is not in the path" do + it "is false" do + comm.stub_command("which rsync", stdout: '', exit_code: 1) + expect(cap.rsync_installed(machine)).to be false + end + end + end + + describe ".rsync_install" do + let(:cap) { caps.get(:rsync_install) } + + it "installs rsync" do + cap.rsync_install(machine) + + expect(comm.received_commands[0]).to match(/opkg update/) + expect(comm.received_commands[0]).to match(/opkg install rsync/) + end + end + + describe ".rsync_command" do + let(:cap) { caps.get(:rsync_command) } + + it "provides the rsync command to use" do + expect(cap.rsync_command(machine)).to eq("rsync -zz") + end + end + + describe ".rsync_pre" do + let(:cap) { caps.get(:rsync_pre) } + + it "creates target directory on guest" do + cap.rsync_pre(machine, :guestpath => guest_directory) + expect(comm.received_commands[0]).to match(/mkdir -p '\/guest\/directory\/path'/) + end + end + + describe ".rsync_post" do + let(:cap) { caps.get(:rsync_post) } + + it "is a no-op" do + cap.rsync_post(machine, {}) + expect(comm).to_not receive(:execute) + end + end +end From 862399905f3b878e73d5b04707cf8cf948693447 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 30 Jul 2020 17:20:29 -0400 Subject: [PATCH 2/2] Detect older versions of OpenWrt --- plugins/guests/openwrt/guest.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/guests/openwrt/guest.rb b/plugins/guests/openwrt/guest.rb index 55f871b6d..73840ca1f 100644 --- a/plugins/guests/openwrt/guest.rb +++ b/plugins/guests/openwrt/guest.rb @@ -1,10 +1,23 @@ -require_relative '../linux/guest' - module VagrantPlugins module GuestOpenWrt - class Guest < VagrantPlugins::GuestLinux::Guest + class Guest # Name used for guest detection GUEST_DETECTION_NAME = "openwrt".freeze + + def detect?(machine) + machine.communicate.test <<~EOH + if test -e /etc/openwrt_release; then + exit + fi + if test -r /etc/os-release; then + source /etc/os-release && test 'x#{self.class.const_get(:GUEST_DETECTION_NAME)}' = "x$ID" && exit + fi + if test -r /etc/banner; then + cat /etc/banner | grep -qi '#{self.class.const_get(:GUEST_DETECTION_NAME)}' && exit + fi + exit 1 + EOH + end end end end