class Chef::Knife::Xargs

Public Instance Methods

create_command(files) click to toggle source
# File lib/chef/knife/xargs.rb, line 153
def create_command(files)
  command = name_args.join(' ')

  # Create the (empty) tempfiles
  tempfiles = {}
  begin
    # Create the temporary files
    files.each do |file|
      tempfile = Tempfile.new(file.name)
      tempfiles[tempfile] = { :file => file }
    end
  rescue
    destroy_tempfiles(files)
    raise
  end

  # Create the command
  paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(' ')
  if config[:replace_all]
    final_command = command.gsub(config[:replace_all], paths)
  elsif config[:replace_first]
    final_command = command.sub(config[:replace_first], paths)
  else
    final_command = "#{command} #{paths}"
  end

  [final_command, tempfiles]
end
destroy_tempfiles(tempfiles) click to toggle source
# File lib/chef/knife/xargs.rb, line 182
def destroy_tempfiles(tempfiles)
  # Unlink the files now that we're done with them
  tempfiles.keys.each { |tempfile| tempfile.close! }
end
get_patterns() click to toggle source
# File lib/chef/knife/xargs.rb, line 142
def get_patterns
  if config[:patterns]
    [ config[:patterns] ].flatten
  elsif config[:null_separator]
    stdin.binmode
    stdin.read.split("\000")
  else
    stdin.read.split(/\s+/)
  end
end
run() click to toggle source
# File lib/chef/knife/xargs.rb, line 73
def run
  error = false
  # Get the matches (recursively)
  files = []
  pattern_args_from(get_patterns).each do |pattern|
    Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
      if result.dir?
        # TODO option to include directories
        ui.warn "#{format_path(result)}: is a directory.  Will not run #{command} on it."
      else
        files << result
        ran = false

        # If the command would be bigger than max command line, back it off a bit
        # and run a slightly smaller command (with one less arg)
        if config[:max_command_line]
          command, tempfiles = create_command(files)
          begin
            if command.length > config[:max_command_line].to_i
              if files.length > 1
                command, tempfiles_minus_one = create_command(files[0..-2])
                begin
                  error = true if xargs_files(command, tempfiles_minus_one)
                  files = [ files[-1] ]
                  ran = true
                ensure
                  destroy_tempfiles(tempfiles)
                end
              else
                error = true if xargs_files(command, tempfiles)
                files = [ ]
                ran = true
              end
            end
          ensure
            destroy_tempfiles(tempfiles)
          end
        end

        # If the command has hit the limit for the # of arguments, run it
        if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
          command, tempfiles = create_command(files)
          begin
            error = true if xargs_files(command, tempfiles)
            files = []
            ran = true
          ensure
            destroy_tempfiles(tempfiles)
          end
        end
      end
    end
  end

  # Any leftovers commands shall be run
  if files.size > 0
    command, tempfiles = create_command(files)
    begin
      error = true if xargs_files(command, tempfiles)
    ensure
      destroy_tempfiles(tempfiles)
    end
  end

  if error
    exit 1
  end
end
sub_filenames(str, tempfiles) click to toggle source
# File lib/chef/knife/xargs.rb, line 257
def sub_filenames(str, tempfiles)
  tempfiles.each_pair do |tempfile, file|
    str = str.gsub(tempfile.path, format_path(file[:file]))
  end
  str
end
xargs_files(command, tempfiles) click to toggle source
# File lib/chef/knife/xargs.rb, line 187
def xargs_files(command, tempfiles)
  error = false
  # Create the temporary files
  tempfiles.each_pair do |tempfile, file|
    begin
      value = file[:file].read
      file[:value] = value
      tempfile.open
      tempfile.write(value)
      tempfile.close
    rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
      ui.error "#{format_path(e.entry)}: #{e.reason}."
      error = true
      tempfile.close!
      tempfiles.delete(tempfile)
      next
    rescue Chef::ChefFS::FileSystem::NotFoundError => e
      ui.error "#{format_path(e.entry)}: No such file or directory"
      error = true
      tempfile.close!
      tempfiles.delete(tempfile)
      next
    end
  end

  return error if error && tempfiles.size == 0

  # Run the command
  if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
    output sub_filenames(command, tempfiles)
  end
  command_output = %x`#{command}`
  command_output = sub_filenames(command_output, tempfiles)
  stdout.write command_output

  # Check if the output is different
  tempfiles.each_pair do |tempfile, file|
    # Read the new output
    new_value = IO.binread(tempfile.path)

    # Upload the output if different
    if config[:force] || new_value != file[:value]
      if config[:dry_run]
        output "Would update #{format_path(file[:file])}"
      else
        file[:file].write(new_value)
        output "Updated #{format_path(file[:file])}"
      end
    end

    # Print a diff of what was uploaded
    if config[:diff] && new_value != file[:value]
      old_file = Tempfile.open(file[:file].name)
      begin
        old_file.write(file[:value])
        old_file.close

        diff = %x`diff -u #{old_file.path} #{tempfile.path}`
        diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
        diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
        stdout.write diff
      ensure
        old_file.close!
      end
    end
  end

  error
end