class PgS3Dumper::Dumper

Attributes

bucket[R]
database_name[R]
database_url[R]
prefix[R]

Public Class Methods

new(options) click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 18
def initialize(options)
  @database_url = options[:database_url] || ENV['DATABASE_URL']
  aws_region = options[:aws_region] || ENV['AWS_REGION']
  aws_key = options[:aws_key] || ENV['AWS_ACCESS_KEY_ID']
  aws_secret = options[:aws_secret] || ENV['AWS_SECRET_ACCESS_KEY']
  aws_url = options[:aws_url] || ENV['AWS_URL']
  @prune = options.has_key?(:prune) ? options[:prune] : false

  v = `pg_dump --version`
  v =~ /pg_dump \(PostgreSQL\) 9\.\d\.\d/ or raise Error, "pg_dump version 9.x.x is required and must be in the PATH"
  database_url or raise Error, "Database URL required."
  aws_url =~ %r{^s3://([^/]+)/(\S*?)/?$} or raise Error, "Invalid AWS URL"

  bucket_name = $1
  @prefix = "#{$2}/"

  database_url =~ %r{postgres://[^/]*/(.+)\??.*$} or raise Error, "Invalid database URL"
  @database_name = $1
  @prefix = File.join(prefix, "#{database_name}/")

  aws_key && aws_secret or raise Error, "AWS key and secret required"

  cred = Aws::Credentials.new(aws_key, aws_secret)
  client = Aws::S3::Client.new(:credentials => cred, :region => aws_region)
  s3 = Aws::S3::Resource.new(:client => client)
  @bucket = s3.bucket(bucket_name)
end

Public Instance Methods

backup() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 74
def backup
  backups = find_backups

  # Keep daily backups for one week
  # Keep weekly backups for one month
  # Keep monthly backups for one year
  7.times.map{|i| i.days.ago}.each do |ts|
    backups.each do |b|
      b.keep_on_day(ts)
      # We kept one backup on day ts, go to next day
      break if b.keep
    end
  end

  4.times.map{|i| i.weeks.ago}.each do |ts|
    backups.each do |b|
      b.keep_on_week(ts)
      # We kept one backup on day ts, go to next day
      break if b.keep
    end
  end

  12.times.map{|i| i.months.ago}.each do |ts|
    backups.each do |b|
      b.keep_on_month(ts)
      # We kept one backup on day ts, go to next day
      break if b.keep
    end
  end


  # Keep all backups for today
  backups.each do |b|
    b.keep_on_day(Date.today)
  end

  # Make new backup
  key = "#{now.iso8601}-#{database_name.split('/').last}.dmp"
  file = Tempfile.new(key)

  puts "## Creating backup"

  system "pg_dump -Fc #{database_url} > #{file.path}" or fail 'Cannot create dump'

  obj = bucket.object(File.join(prefix, key))

  obj.upload_file(file.path, :metadata => {:backup_id => Backup.generate_id})
  file.close
  file.unlink
  bck = Backup.new(obj)
  puts "Created backup: #{bck}"

  if prune?
    puts "## Pruning old backups"
    backups.each do |b|
      b.prune
    end
  end
end
cleanup() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 68
def cleanup
  find_backups.each do |b|
    b.delete
  end
end
list() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 59
def list
  f = "% 12s% 30s"
  puts f % ['id', 'date']
  puts "-" * 42
  find_backups.each do |b|
    puts f % [b.short_id, b.ts]
  end
end
now() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 134
def now
  Time.now.utc
end
prune?() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 14
def prune?
  @prune
end
run(command) click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 46
def run(command)
  case command
  when :list
    list
  when :backup
    backup
  when :cleanup
    cleanup
  else
    raise Error, "Invalid command '#{command}'"
  end
end

Private Instance Methods

find_backups() click to toggle source
# File lib/pg_s3_dumper/dumper.rb, line 139
def find_backups
  backups = []

  # Collect existing backups
  bucket.objects(:prefix => prefix).each do |o|
    backups << Backup.new(o.object)
  end

  backups.sort
end