Class: EY::Backup::Postgresql

Inherits:
Engine show all
Defined in:
lib/ey_backup/engines/postgresql_engine.rb

Constant Summary

Constants included from Spawner

Spawner::CHUNK_SIZE

Instance Attribute Summary

Attributes inherited from Engine

#allow_concurrent, #force, #host, #key_id, #log_coordinates, #password, #skip_analyze, #username

Instance Method Summary collapse

Methods inherited from Engine

#backup_running?, #block_concurrent, descendants, #gpg?, inherited, #initialize, label, lookup, register

Methods included from Spawner

#ioify, #run, #runs?, #spawn

Methods inherited from Base

#logger

Constructor Details

This class inherits a constructor from EY::Backup::Engine

Instance Method Details

#cancel_connections(database_name) ⇒ Object



72
73
74
75
76
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 72

def cancel_connections(database_name)
  %x{psql -U#{username} -h #{host} postgres -c"SELECT pg_terminate_backend(pg_stat_activity.pid)
    FROM pg_stat_activity
    WHERE pg_stat_activity.datname = '#{database_name}';"}
end

#check_connections(database_name) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 52

def check_connections(database_name)
  active_connections = %x{PGPASSWORD='#{password}' psql -U#{username} -h #{host} -t postgres -c "select count(*) from pg_stat_activity where datname='#{database_name}';"}
  
  if active_connections.to_i > 0
    res = ''
    unless force
      puts "There are currently #{active_connections} connections on database: '#{database_name}'; can I kill these to continue (Y/n):"
      Timeout::timeout(30){
        res = gets.strip
      }
    end
    
    if res.upcase == 'Y' or force
      cancel_connections(database_name)
    else
      EY::Backup.logger.fatal(%Q{ERROR: Target database has active connections. For more information, see "Restore or load a database" in docs.engineyard.com})
    end
  end
end

#check_if_replicaObject



90
91
92
93
94
95
96
97
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 90

def check_if_replica
  command = "PGPASSWORD='#{password}' psql -U#{username} -h #{host} -t -c 'select pg_is_in_recovery()' postgres| head -n 1"
  verbose "Checking for replica state with: #{command}"
  stdout = %x{#{command}}
  unless stdout.chomp =~ /^\W*f$/
    EY::Backup.logger.fatal(%Q{ERROR: Target host: '#{host}' is currently a replica in recovery mode; restore operations need to be processed against the master.})
  end
end

#create_command(database_name) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 106

def create_command(database_name)
  command="PGPASSWORD='#{password}' psql -U#{username} -h #{host} -t postgres -c \"SELECT 'CREATE DATABASE ' || datname ||
    ' WITH OWNER ' || pg_user.usename ||
    CASE (select pg_encoding_to_char(encoding) from pg_database where datname='template1') 
    WHEN pg_encoding_to_char(encoding) 
      THEN ''
      ELSE ' template=template0'
    END ||
    ' ENCODING ''' || pg_encoding_to_char(encoding) ||
    ''' LC_COLLATE ''' || datcollate||
    ''' LC_CTYPE ''' || datctype || 
    ''' CONNECTION LIMIT ' || datconnlimit || ';'
  FROM pg_database
  INNER JOIN pg_user
    ON pg_user.usesysid = pg_database.datdba
  WHERE datname = '#{database_name}'\""
  verbose "Getting create info: #{command}"
  %x{#{command}}
end

#create_database(database_name) ⇒ Object



84
85
86
87
88
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 84

def create_database(database_name)
  command = "PGPASSWORD='#{password}' createdb -U#{username} -h #{host} #{database_name}"
  verbose "Creating Database with: #{command}"
  %x{#{command}}
end

#cycle_database(database_name) ⇒ Object



126
127
128
129
130
131
132
133
134
135
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 126

def cycle_database(database_name)
  create_cmd = create_command(database_name).chomp
  if create_cmd == ''
    create_database(database_name)
  else
    check_connections(database_name)
    drop_database(database_name)
    %x{PGPASSWORD='#{password}' psql -U#{username} -h #{host} -t postgres -c "#{create_cmd}"}
  end
end

#drop_database(database_name) ⇒ Object



78
79
80
81
82
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 78

def drop_database(database_name)
  command = "PGPASSWORD='#{password}' dropdb -h #{host} -U#{username} #{database_name}"
  verbose "Dropping Database with: #{command}"
  %x{#{command}}
end

#dump(database_name, basename) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 6

def dump(database_name, basename)
  file = basename + '.dump'

  command = "PGPASSWORD='#{password}' pg_dump -h #{host} --create --format=c -U#{username} #{database_name} "

  if gpg?
    command << " | " << GPGEncryptor.command_for(key_id)
    file << GPGEncryptor.extension
  end

  command << " > #{file}"

  block_concurrent(database_name) unless allow_concurrent
  run(command, database_name)

  file
end

#load(database_name, file) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 24

def load(database_name, file)
  if file =~ /.gpz$/ # GPG?
    abort "\nCannot restore a GPG backup directly; decrypt the file (#{file}) using your key and then load with pg_restore.
  To decrypt a backup: https://support.cloud.engineyard.com/hc/en-us/articles/205413948-Use-PGP-Encrypted-Database-Backups-with-Engine-Yard-Cloud#restore
  Once decrypted, restore with: `pg_restore -h #{host} --format=c --clean -U#{username} -d #{database_name} <filename>`\n\n"
  end

  cycle_database(database_name)
  
  # Exclude Extension Comments
  toc_file = "#{file}.toc"
  table_of_contents(file, toc_file)

  command = "cat #{file}"

  command << " | PGPASSWORD='#{password}' pg_restore -L #{toc_file} -h #{host} --format=c -U#{username} -d #{database_name}"

  run(command, database_name)
  
  system("rm #{toc_file}") if File.exists?(toc_file)
  
  # Analyze database unless disabled
  unless skip_analyze
    verbose "Analyzing database '#{database_name}', use --skip-analyze to skip this step."
    %x{PGPASSWORD='#{password}' vacuumdb -h #{host} -U#{username} -d #{database_name} --analyze-only}
  end
end

#suffixObject



137
138
139
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 137

def suffix
  /\.(dump|gpz)$/
end

#table_of_contents(file, toc_file) ⇒ Object



99
100
101
102
103
104
# File 'lib/ey_backup/engines/postgresql_engine.rb', line 99

def table_of_contents(file, toc_file)
  command = %Q{pg_restore -l #{file} | sed -e 's/^\\\(.* COMMENT - EXTENSION .*\\\)/;\\1/g' \
    -e 's/^\\\(.* rdsadmin$\\\)/;\\1/g' > #{toc_file}}
  verbose "Creating table of contents: #{command}"
  %x{#{command}}
end