#!/usr/bin/ruby


# BEGIN: Helper Functions

def usage
  puts "#{__FILE__} [ACTION <arg>] [<flags>]

Description:
  This utlity is meant to be used to interact with & manage the filebucket on P4 nodes due to the 
  utility for that `puppet filebucket -l <action>` being nonfunctional.

  This implements the same functionality (minus the puppet tie-in) and will allow the user to 
  search the filebucket and restore from it.

Actions:
  search <term>            : Search for bucket entries matching a portion of the filepath
  list                      : List all Bucket entries
  list-files                : List all files/paths that have been backed up to the bucket
  get <entry-hash>          : Get the content of a specific entry (by hash)
  restore-hash <entry-hash> : Restore previous state of file stored in bucket (by-hash)
  restore-file <filepath>   : Restore previous state of file stored in bucket (by-filepath/filename)

Global Flags:
  -d | --debug              : Set debug flag
  -h | --help               : This help message

Author:
  Name: Tristan Ancelet
  Email: tristan.ancelet@acumera.com
  Phone (Work) #: +1 (337) 965-1855

"
end

def log (message, level = 0)
  if $DEBUG == true or $CONFIG[:log_file] != ""
    if message == ""
      puts "log was called without providing a message"
      exit
    end

    case level
      when 0
        level="INFO"
      when 1
        level="WARN"
      when 2
        level="CRIT"
      else
        level="UNDF"
    end
    datestamp=Time.now
    log_message="#{datestamp} : #{$HOSTNAME} : #{level} : #{caller[0]} : #{message}"

    if $CONFIG[:log_file] != ""
      File.open($CONFIG[:log_file],'a') do |file|
        file.write("#{log_message}\n")
      end
    else
      puts log_message
    end
  end
end

def get_verification (prompt = "Do you want to continue?")
  while true
    puts "#{prompt} (y/n): "
    answer = STDIN.gets
    answer = answer.strip()
    case answer
      when 'y','yes'
        return true
      when 'n', 'no'
        return false
      else
        puts "#{answer} is not an acceptible answer"
    end
  end
end

# END: Helper Functions



# BEGIN: Variables

if not (ARGV & ['-h', '--help']).empty?
  usage
  exit
end

if ENV["USER"] != 'root'
  puts "This script should only be run by root (permissions issues). Please rerun it as root or prepend \"sudo\""
  exit
end


$DEBUG=false

$CONFIG = Hash.new
$CONFIG[:bucket_dir]=` puppet agent --configprint clientbucketdir `.strip()
$CONFIG[:action]=""
$CONFIG[:search_term]=""
$CONFIG[:log_file]=""
File.open('/etc/hostname') do |file|
  $HOSTNAME=file.read().strip()
end
FLAG_REGEX=/\-+\S+/

# END: Variables



# BEGIN: Handle CLI Args


if ARGV.count == 0
  puts "No arguments were provided"
  usage
  exit
end

if not (ARGV & ['-d', '--debug']).empty?
  $DEBUG=true
end
i=0
case ARGV[i] 
  when 'search', 'get', 'restore'
    $CONFIG[:action]=ARGV[i]
    log "$CONFIG[:action] was set to #{ARGV[i]}"
    log "user provided search action ARGV[i.next] == #{ARGV[i.next]}"
    if ARGV[i.next] != "" and not ARGV[i.next] =~ FLAG_REGEX
      $CONFIG[:search_term]=ARGV[i.next]
      log "search_term was set to #{ARGV[i.next]}"
      i+=2
    else
      puts "Flag[#{ARGV[i]}] : Argument[#{ARGV[i.next]}] : Either the argument was not provided or it was a flag"
      usage
      exit
    end

  when 'list', 'list-files'
    $CONFIG[:action] = ARGV[i]
    log "$CONFIG[:action] was set to #{ARGV[i]}"
    i+=1

  else
    puts "#{ARGV[i]} is not a valid action. Please make sure you use a valid action as the first argument of the script"
    usage
    exit
end

## BEGIN: Checks

if $CONFIG[:action] == ""
  puts "Action was not provided"
end

case $CONFIG[:action]
  when 'search', 'get'
    if $CONFIG[:search_term] == ""
      puts "Search Term was not provided"
      usage
      exit
    end
end

## END: Checks

# END: Handle CLI Args


# BEGIN: Classes

class BucketEntry
  attr_reader :hash, :filepaths, :mtime
  def initialize (entry_dir)
    @entry_dir = entry_dir
    @hash = File.basename(entry_dir)
    @filepaths = Array.new
    File.open("#{entry_dir}/paths") do |file|
      file.read().split(/\n/).each do |path|
        log "BucketEntry[#{@hash}] adding #{path} to @filepaths"
        @filepaths.push(path)
      end
    end
    @mtime = File.mtime(entry_dir)
    log "BucketEntry was created from #{entry_dir}"
  end

  def path_include? (path_string)
    log "BucketEntry[#{hash}] was called with #{path_string}"
    @filepaths.each.any? {|path| path.include? path_string}
  end

  def infostring
    "Entry [#{@hash}]:
      Paths: #{@filepaths.join(',')}
      MTIME: #{@mtime}

"
  end

  def inline_info
    "#{@mtime} : #{@hash} : #{@filepaths.join(',')}"
  end

  def content 
    log "BucketEntry[#{@hash}] getting contents"
    File.open("#{@entry_dir}/contents",'r') do |file|
      file.read()
    end
  end

end

class Bucket
  attr_reader :bucketdir, :entries
  def initialize (clientbucketdir)
    log "Bucket is being created from #{clientbucketdir}"
    @bucketdir = clientbucketdir
    @entries = Hash.new
    load_bucket
  end

  def load_bucket
    log "Bucket[#{@bucketdir}] is loading entries"
    Dir["#{@bucketdir}/**/paths"].each.map{|path| File.dirname(path)}.each do |directory|
      log "\"#{directory}\" was grabbed from bucket directory. Making new BucketEntry"
      entry = BucketEntry.new(directory)
      @entries[entry.hash]=entry
      log "BucketEntry[#{entry.hash}] was added to @entries Size=#{@entries.count()}"
    end
    log "Bucket[#{@bucketdir}] was loaded"
  end
end

# END: Classes



# BEGIN: Work Functions

def search_entries_paths (bucket)
  log "user entered"
  puts bucket.entries.each_value.select {|entry| entry.path_include? $CONFIG[:search_term]}.map{|entry| entry.inline_info}.sort.join("\n")
end

def get_content_of_entry_hash (bucket)
  log "user entered"
  if bucket.entries.has_key? $CONFIG[:search_term]
    puts bucket.entries[$CONFIG[:search_term]].content
  else
    puts "There were no entries corresponding to #{$CONFIG[:search_term]}"
    exit
  end
end

def list_all_entries (bucket)
  puts bucket.entries.each_value.each.map{|entry| entry.inline_info}.sort.join("\n")
end

def list_entry_files (bucket)
  filenames = Array.new
  bucket.entries.each_value do |entry| 
    entry.filepaths.each do |path|
      if not filenames.include? path
        filenames.push(path)
      end
    end
  end
  puts filenames.sort.join("\n")
end

def restore_entry (bucket)
  if bucket.entries.has_key? $CONFIG[:search_term]
    entry = bucket.entries[$CONFIG[:search_term]]
    if entry.filepaths.count == 1
      filepath = entry.filepaths[0]
      if filepath[0] != '/'
        filepath = "/#{filepath}"
      end
      if get_verification "Are you sure you want to overwrite #{filepath}?" 
        File.open(filepath,'w') do |file|
          file.write(entry.content)
        end
      else
        puts "Ok not overwriting."
      end
    else
    end
  else
    puts "There were no entries corresponding to #{$CONFIG[:search_term]}"
  end
end

# END: Work Functions


# BEGIN: Work

if __FILE__ == $0
  bucket = Bucket.new($CONFIG[:bucket_dir])

  case $CONFIG[:action]
    when 'search'
      search_entries_paths bucket

    when 'get'
      get_content_of_entry_hash bucket

    when 'list'
      list_all_entries bucket

    when 'list-files'
      list_entry_files bucket

    when 'restore'
      restore_entry bucket

  end

end
# END: Work