class Chef::Provider::File

Attributes

deployment_strategy[R]
needs_creating[RW]
needs_unlinking[RW]

Public Class Methods

new(new_resource, run_context) click to toggle source
Calls superclass method Chef::Provider::new
# File lib/chef/provider/file.rb, line 64
def initialize(new_resource, run_context)
  @content_class ||= Chef::Provider::File::Content
  if new_resource.respond_to?(:atomic_update)
    @deployment_strategy = Chef::FileContentManagement::Deploy.strategy(new_resource.atomic_update)
  end
  super
end

Public Instance Methods

define_resource_requirements() click to toggle source
# File lib/chef/provider/file.rb, line 110
def define_resource_requirements
  # deep inside FAC we have to assert requirements, so call FACs hook to set that up
  access_controls.define_resource_requirements

  # Make sure the parent directory exists, otherwise fail.  For why-run assume it would have been created.
  requirements.assert(:create, :create_if_missing, :touch) do |a|
    parent_directory = ::File.dirname(new_resource.path)
    a.assertion { ::File.directory?(parent_directory) }
    a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.")
    a.whyrun("Assuming directory #{parent_directory} would have been created")
  end

  # Make sure the file is deletable if it exists, otherwise fail.
  if ::File.exist?(new_resource.path)
    requirements.assert(:delete) do |a|
      a.assertion { ::File.writable?(new_resource.path) }
      a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{new_resource.path} exists but is not writable so it cannot be deleted")
    end
  end

  error, reason, whyrun_message = inspect_existing_fs_entry
  requirements.assert(:create) do |a|
    a.assertion { error.nil? }
    a.failure_message(error, reason)
    a.whyrun(whyrun_message)
    # Subsequent attempts to read the fs entry at the path (e.g., for
    # calculating checksums) could blow up, so give up trying to continue
    # why-running.
    a.block_action!
  end
end
load_current_resource() click to toggle source
# File lib/chef/provider/file.rb, line 72
def load_current_resource
  # true if there is a symlink and we need to manage what it points at
  @managing_symlink = file_class.symlink?(new_resource.path) && ( new_resource.manage_symlink_source || new_resource.manage_symlink_source.nil? )

  # true if there is a non-file thing in the way that we need to unlink first
  @needs_unlinking =
    if ::File.exist?(new_resource.path)
      if managing_symlink?
        !symlink_to_real_file?(new_resource.path)
      else
        !real_file?(new_resource.path)
      end
    else
      false
    end

  # true if we are going to be creating a new file
  @needs_creating = !::File.exist?(new_resource.path) || needs_unlinking?

  # Let children resources override constructing the current_resource
  @current_resource ||= Chef::Resource::File.new(new_resource.name)
  current_resource.path(new_resource.path)

  unless needs_creating?
    # we are updating an existing file
    if managing_content?
      logger.trace("#{new_resource} checksumming file at #{new_resource.path}.")
      current_resource.checksum(checksum(current_resource.path))
    else
      # if the file does not exist or is not a file, then the checksum is invalid/pointless
      current_resource.checksum(nil)
    end
    load_resource_attributes_from_file(current_resource)
  end

  current_resource
end

Private Instance Methods

content() click to toggle source
# File lib/chef/provider/file.rb, line 277
def content
  @content ||= begin
    load_current_resource if current_resource.nil?
    @content_class.new(new_resource, current_resource, @run_context, logger)
  end
end
contents_changed?() click to toggle source
# File lib/chef/provider/file.rb, line 451
def contents_changed?
  logger.trace "calculating checksum of #{tempfile.path} to compare with #{current_resource.checksum}"
  tempfile_checksum != current_resource.checksum
end
diff() click to toggle source
# File lib/chef/provider/file.rb, line 380
def diff
  @diff ||= Chef::Util::Diff.new
end
do_acl_changes() click to toggle source
# File lib/chef/provider/file.rb, line 443
def do_acl_changes
  if access_controls.requires_changes?
    converge_by(access_controls.describe_changes) do
      access_controls.set_all
    end
  end
end
do_backup(file = nil) click to toggle source
# File lib/chef/provider/file.rb, line 376
def do_backup(file = nil)
  Chef::Util::Backup.new(new_resource, file).backup!
end
do_contents_changes() click to toggle source
# File lib/chef/provider/file.rb, line 394
      def do_contents_changes
        # a nil tempfile is okay, means the resource has no content or no new content
        return if tempfile.nil?
        # but a tempfile that has no path or doesn't exist should not happen
        if tempfile.path.nil? || !::File.exist?(tempfile.path)
          raise "#{ChefUtils::Dist::Infra::CLIENT} is confused, trying to deploy a file that has no path or does not exist..."
        end

        # the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed
        if ::File.file?(new_resource.path) && contents_changed?
          description = [ "update content in file #{new_resource.path} from \
#{short_cksum(current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ]

          # Hide the diff output if the resource is marked as a sensitive resource
          if new_resource.sensitive
            new_resource.diff("suppressed sensitive resource")
            description << "suppressed sensitive resource"
          else
            diff.diff(current_resource.path, tempfile.path)
            new_resource.diff( diff.for_reporting ) unless needs_creating?
            description << diff.for_output
          end

          converge_by(description) do
            update_file_contents
          end
        end

        # unlink necessary to clean up in why-run mode
        tempfile.close
        tempfile.unlink
      end
do_create_file() click to toggle source
# File lib/chef/provider/file.rb, line 367
def do_create_file
  if needs_creating?
    converge_by("create new file #{new_resource.path}") do
      deployment_strategy.create(new_resource.path)
      logger.info("#{new_resource} created file #{new_resource.path}")
    end
  end
end
do_generate_content() click to toggle source
# File lib/chef/provider/file.rb, line 329
def do_generate_content
  # referencing the tempfile magically causes content to be generated
  tempfile
end
do_selinux(recursive = false) click to toggle source

This logic ideally will be made into some kind of generic platform-dependent post-converge hook for file-like resources, but for now we only have the single selinux use case.

# File lib/chef/provider/file.rb, line 431
def do_selinux(recursive = false)
  if resource_updated? && Chef::Config[:enable_selinux_file_permission_fixup]
    if selinux_enabled?
      converge_by("restore selinux security context") do
        restore_security_context(::File.realpath(new_resource.path), recursive)
      end
    else
      logger.trace "selinux utilities can not be found. Skipping selinux permission fixup."
    end
  end
end
do_validate_content() click to toggle source
# File lib/chef/provider/file.rb, line 338
def do_validate_content
  if new_resource.checksum && tempfile && ( new_resource.checksum != tempfile_checksum )
    raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum))
  end

  if tempfile && contents_changed?
    new_resource.verify.each do |v|
      unless v.verify(tempfile.path)
        backupfile = "#{Chef::Config[:file_cache_path]}/failed_validations/#{::File.basename(tempfile.path)}"
        FileUtils.mkdir_p ::File.dirname(backupfile)
        FileUtils.cp tempfile.path, backupfile
        raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{new_resource.sensitive ? "[sensitive]" : "#{v}\n#{v.output}"}\nTemporary file moved to #{backupfile}"
      end
    end
  end
end
file_type_string(path) click to toggle source
# File lib/chef/provider/file.rb, line 284
def file_type_string(path)
  case
  when ::File.blockdev?(path)
    "block device"
  when ::File.chardev?(path)
    "char device"
  when ::File.directory?(path)
    "directory"
  when ::File.pipe?(path)
    "pipe"
  when ::File.socket?(path)
    "socket"
  when file_class.symlink?(path)
    "symlink"
  else
    "unknown filetype"
  end
end
inspect_existing_fs_entry() click to toggle source

Handles resource requirements for action :create when some fs entry already exists at the destination path. For actions other than create, we don't care what kind of thing is at the destination path because:

  • for :create_if_missing, we're assuming the user wanted to avoid blowing away the non-file here

  • for :touch, we can modify perms of whatever is at this path, regardless of its type

  • for :delete, we can blow away whatever is here, regardless of its type

For the action :create case, we need to deal with user-selectable behavior to see if we're in an error condition.

  • If there's no file at the destination path currently, we're cool to create it.

  • If the fs entry that currently exists at the destination is a regular file, we're cool to update it with new content.

  • If the fs entry is a symlink AND the resource has `manage_symlink_source` enabled, we need to verify that the symlink is a valid pointer to a real file. If it is, we can manage content and permissions on the symlink source, otherwise, error.

  • If `manage_symlink_source` is not enabled, fall through.

  • If force_unlink is true, action :create will unlink whatever is in the way.

  • If force_unlink is false, we're in an exceptional situation, so we want to error.

Note that this method returns values to be used with requirement assertions, which then decide whether or not to raise or issue a warning for whyrun mode.

# File lib/chef/provider/file.rb, line 224
def inspect_existing_fs_entry
  path = new_resource.path

  if !l_exist?(path)
    [nil, nil, nil]
  elsif real_file?(path)
    [nil, nil, nil]
  elsif file_class.symlink?(path) && new_resource.manage_symlink_source
    verify_symlink_sanity(path)
  elsif file_class.symlink?(new_resource.path) && new_resource.manage_symlink_source.nil?
    logger.warn("File #{path} managed by #{new_resource} is really a symlink (to #{file_class.realpath(new_resource.path)}). Managing the source file instead.")
    logger.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
    logger.warn("In a future release, 'manage_symlink_source' will not be enabled by default")
    verify_symlink_sanity(path)
  elsif new_resource.force_unlink
    [nil, nil, nil]
  else
    [ Chef::Exceptions::FileTypeMismatch,
      "File #{path} exists, but is a #{file_type_string(new_resource.path)}, set force_unlink to true to remove",
      "Assuming #{file_type_string(new_resource.path)} at #{new_resource.path} would have been removed by a previous resource",
    ]
  end
end
l_exist?(path) click to toggle source

Similar to File.exist?, but also returns true in the case that the named file is a broken symlink.

# File lib/chef/provider/file.rb, line 316
def l_exist?(path)
  ::File.exist?(path) || file_class.symlink?(path)
end
load_resource_attributes_from_file(resource) click to toggle source
# File lib/chef/provider/file.rb, line 460
def load_resource_attributes_from_file(resource)
  if ChefUtils.windows?
    # This is a work around for CHEF-3554.
    # OC-6534: is tracking the real fix for this workaround.
    # Add support for Windows equivalent, or implicit resource
    # reporting won't work for Windows.
    return
  end

  acl_scanner = ScanAccessControl.new(new_resource, resource)
  acl_scanner.set_all!
end
managing_content?() click to toggle source

What to check in this resource to see if we're going to be actively managing content (for things like doing checksums in load_current_resource). Expected to be overridden in subclasses.

# File lib/chef/provider/file.rb, line 192
def managing_content?
  return true if new_resource.checksum
  return true if !new_resource.content.nil? && @action != :create_if_missing

  false
end
needs_creating?() click to toggle source
# File lib/chef/provider/file.rb, line 477
def needs_creating?
  !!@needs_creating
end
needs_unlinking?() click to toggle source
# File lib/chef/provider/file.rb, line 481
def needs_unlinking?
  !!@needs_unlinking
end
real_file?(path) click to toggle source
# File lib/chef/provider/file.rb, line 303
def real_file?(path)
  !file_class.symlink?(path) && ::File.file?(path)
end
tempfile() click to toggle source
# File lib/chef/provider/file.rb, line 456
def tempfile
  @tempfile ||= content.tempfile
end
tempfile_checksum() click to toggle source
# File lib/chef/provider/file.rb, line 334
def tempfile_checksum
  @tempfile_checksum ||= checksum(tempfile.path)
end
update_file_contents() click to toggle source
# File lib/chef/provider/file.rb, line 384
def update_file_contents
  do_backup unless needs_creating?
  deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path).force_encoding(Chef::Config[:ruby_encoding]))
  logger.info("#{new_resource} updated file contents #{new_resource.path}")
  if managing_content?
    # save final checksum for reporting.
    new_resource.final_checksum = checksum(new_resource.path)
  end
end