summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2020-08-28 08:40:16 -0700
committerGitHub <noreply@github.com>2020-08-28 08:40:16 -0700
commit19066bd9430baea8d63e8981515d78d7c5ca2997 (patch)
treeb0d8325a0377f767dbd201c9a5abc63e0544a420
parenta92e03c00eb7633e529732a12cbc6a56646e9187 (diff)
parent4e6234d500475ec772ae2f7d81bfb91067b4a12c (diff)
downloadchef-19066bd9430baea8d63e8981515d78d7c5ca2997.tar.gz
Merge pull request #10171 from MsysTechnologiesllc/antima/bind_mounts_not_idempotent_fixes
Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/_mount.rb20
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/linux.rb1
-rw-r--r--lib/chef/provider/mount/linux.rb63
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--spec/unit/provider/mount/linux_spec.rb97
5 files changed, 182 insertions, 0 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_mount.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_mount.rb
new file mode 100644
index 0000000000..207ef9d1fb
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/_mount.rb
@@ -0,0 +1,20 @@
+mount "/proc" do
+ device "proc"
+ fstype "proc"
+ options %w{bind rw}
+ action %i{ mount enable }
+end
+
+mount "/mnt" do
+ device "/tmp"
+ fstype "ext4"
+ options %w{bind rw}
+ action %i{ mount enable }
+end
+
+mount "/mnt" do
+ device "/etc"
+ fstype "ext4"
+ options %w{bind rw}
+ action %i{ mount enable }
+end \ No newline at end of file
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
index 8fae36662c..ba1f5e84f7 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
@@ -122,6 +122,7 @@ include_recipe "::_cron"
include_recipe "::_ohai_hint"
include_recipe "::_openssl"
include_recipe "::_tests"
+include_recipe "::_mount"
# at the moment these do not run properly in docker
# we need to investigate if this is a snap on docker issue or a chef issue
diff --git a/lib/chef/provider/mount/linux.rb b/lib/chef/provider/mount/linux.rb
new file mode 100644
index 0000000000..3199024f1b
--- /dev/null
+++ b/lib/chef/provider/mount/linux.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Antima Gupta (<agupta@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../mount"
+
+class Chef
+ class Provider
+ class Mount
+ class Linux < Chef::Provider::Mount::Mount
+
+ provides :mount, os: "linux"
+
+ # Check to see if the volume is mounted.
+ # "findmnt" outputs the mount points with volume.
+ # Convert the mount_point of the resource to a real path in case it
+ # contains symlinks in its parents dirs.
+
+ def mounted?
+ mounted = false
+
+ real_mount_point = if ::File.exists? @new_resource.mount_point
+ ::File.realpath(@new_resource.mount_point)
+ else
+ @new_resource.mount_point
+ end
+
+ shell_out!("findmnt -rn").stdout.each_line do |line|
+ case line
+ # Permalink for device already mounted to mount point for : https://rubular.com/r/L0RNnD4gf2DJGl
+ when /\A#{Regexp.escape(real_mount_point)}\s+#{device_mount_regex}\s/
+ mounted = true
+ logger.trace("Special device #{device_logstring} mounted as #{real_mount_point}")
+ # Permalink for multiple devices mounted to the same mount point(i.e. '/proc') https://rubular.com/r/a356yzspU7N9TY
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\s}
+ mounted = false
+ logger.trace("Special device #{$~[1]} mounted as #{real_mount_point}")
+ # Permalink for bind device mounted to an existing mount point: https://rubular.com/r/QAE0ilL3sm3Ldz
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\[#{device_mount_regex}\]\s}
+ mounted = true
+ logger.trace("Bind device #{device_logstring} mounted as #{real_mount_point}")
+ end
+ end
+ @current_resource.mounted(mounted)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index d2e9f1991b..6b099fc2b7 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -119,6 +119,7 @@ require_relative "provider/mount/mount"
require_relative "provider/mount/aix"
require_relative "provider/mount/solaris"
require_relative "provider/mount/windows"
+require_relative "provider/mount/linux"
require_relative "provider/remote_file/ftp"
require_relative "provider/remote_file/sftp"
diff --git a/spec/unit/provider/mount/linux_spec.rb b/spec/unit/provider/mount/linux_spec.rb
new file mode 100644
index 0000000000..1141175780
--- /dev/null
+++ b/spec/unit/provider/mount/linux_spec.rb
@@ -0,0 +1,97 @@
+require "spec_helper"
+
+describe Chef::Provider::Mount::Linux do
+
+ let(:run_context) do
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ end
+
+ let(:new_resource) do
+ new_resource = Chef::Resource::Mount.new("/tmp/foo")
+ new_resource.device "/dev/sdz1"
+ new_resource.device_type :device
+ new_resource.fstype "ext3"
+ new_resource.supports remount: false
+ new_resource
+ end
+
+ let(:provider) do
+ Chef::Provider::Mount::Linux.new(new_resource, run_context)
+ end
+
+ before(:each) do
+ allow(::File).to receive(:exists?).with("/dev/sdz1").and_return true
+ allow(::File).to receive(:exists?).with("/tmp/foo").and_return true
+ allow(::File).to receive(:realpath).with("/dev/sdz1").and_return "/dev/sdz1"
+ allow(::File).to receive(:realpath).with("/tmp/foo").and_return "/tmp/foo"
+ end
+
+ context "to see if the volume is mounted" do
+
+ it "should set mounted true if the mount point is found in the mounts list" do
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: "/tmp/foo /dev/sdz1 type ext3 (rw)\n"))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted false if another mount point beginning with the same path is found in the mounts list" do
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: "/tmp/foobar /dev/sdz1 type ext3 (rw)\n"))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_falsey
+ end
+
+ it "should set mounted true if the symlink target of the device is found in the mounts list" do
+ # expand the target path to correct specs on Windows
+ target = ::File.expand_path("/dev/mapper/target")
+
+ allow(::File).to receive(:symlink?).with((new_resource.device).to_s).and_return(true)
+ allow(::File).to receive(:readlink).with((new_resource.device).to_s).and_return(target)
+
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: "/tmp/foo #{target} type ext3 (rw)\n"))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted true if the symlink target of the device is relative and is found in the mounts list - CHEF-4957" do
+ target = "xsdz1"
+
+ # expand the target path to correct specs on Windows
+ absolute_target = ::File.expand_path("/dev/xsdz1")
+
+ allow(::File).to receive(:symlink?).with((new_resource.device).to_s).and_return(true)
+ allow(::File).to receive(:readlink).with((new_resource.device).to_s).and_return(target)
+
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: "/tmp/foo #{absolute_target} type ext3 (rw)\n"))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted true if the mount point is found last in the mounts list" do
+ mount = "#{new_resource.mount_point} /dev/sdy1 type ext3 (rw)\n"
+ mount << "#{new_resource.mount_point} #{new_resource.device} type ext3 (rw)\n"
+
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: mount))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted false if the mount point is not last in the mounts list" do
+ mount = "#{new_resource.device} on #{new_resource.mount_point} type ext3 (rw)\n"
+ mount << "/dev/sdy1 on #{new_resource.mount_point} type ext3 (rw)\n"
+
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: mount))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_falsey
+ end
+
+ it "mounted should be false if the mount point is not found in the mounts list" do
+ allow(provider).to receive(:shell_out!).and_return(double(stdout: "/dev/sdy1 on /tmp/foo type ext3 (rw)\n"))
+ provider.load_current_resource
+ expect(provider.current_resource.mounted).to be_falsey
+ end
+
+ end
+
+end