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 62
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

action_create() click to toggle source
# File lib/chef/provider/file.rb, line 140
def action_create
  do_generate_content
  do_validate_content
  do_unlink
  do_create_file
  do_contents_changes
  do_acl_changes
  do_selinux
  load_resource_attributes_from_file(new_resource) unless Chef::Config[:why_run]
end
action_create_if_missing() click to toggle source
# File lib/chef/provider/file.rb, line 151
def action_create_if_missing
  unless ::File.exist?(new_resource.path)
    action_create
  else
    logger.trace("#{new_resource} exists at #{new_resource.path} taking no action.")
  end
end
action_delete() click to toggle source
# File lib/chef/provider/file.rb, line 159
def action_delete
  if ::File.exists?(new_resource.path)
    converge_by("delete file #{new_resource.path}") do
      do_backup unless file_class.symlink?(new_resource.path)
      ::File.delete(new_resource.path)
      logger.info("#{new_resource} deleted file at #{new_resource.path}")
    end
  end
end
action_touch() click to toggle source
# File lib/chef/provider/file.rb, line 169
def action_touch
  action_create
  converge_by("update utime on file #{new_resource.path}") do
    time = Time.now
    ::File.utime(time, time, new_resource.path)
    logger.info("#{new_resource} updated atime and mtime to #{time}")
  end
end
define_resource_requirements() click to toggle source
# File lib/chef/provider/file.rb, line 108
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 70
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)

  if !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 274
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 445
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 374
def diff
  @diff ||= Chef::Util::Diff.new
end
do_acl_changes() click to toggle source
# File lib/chef/provider/file.rb, line 437
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 370
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 388
      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.exists?(tempfile.path)
          raise "#{Chef::Dist::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 361
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 326
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 425
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 335
def do_validate_content
  if new_resource.checksum && tempfile && ( new_resource.checksum.downcase != tempfile_checksum )
    raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum))
  end

  if tempfile
    new_resource.verify.each do |v|
      if ! v.verify(tempfile.path)
        raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{new_resource.sensitive ? '[sensitive]' : v}"
      end
    end
  end
end
file_type_string(path) click to toggle source
# File lib/chef/provider/file.rb, line 281
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 221
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 313
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 454
def load_resource_attributes_from_file(resource)
  if Chef::Platform.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 190
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 470
def needs_creating?
  !!@needs_creating
end
needs_unlinking?() click to toggle source
# File lib/chef/provider/file.rb, line 474
def needs_unlinking?
  !!@needs_unlinking
end
real_file?(path) click to toggle source
# File lib/chef/provider/file.rb, line 300
def real_file?(path)
  !file_class.symlink?(path) && ::File.file?(path)
end
tempfile() click to toggle source
# File lib/chef/provider/file.rb, line 450
def tempfile
  @tempfile ||= content.tempfile
end
tempfile_checksum() click to toggle source
# File lib/chef/provider/file.rb, line 331
def tempfile_checksum
  @tempfile_checksum ||= checksum(tempfile.path)
end
update_file_contents() click to toggle source
# File lib/chef/provider/file.rb, line 378
def update_file_contents
  do_backup unless needs_creating?
  deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path))
  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