# 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 = %xdiff -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