bibliotech 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/bibliotech +5 -0
- data/doc/example_config_file.yml +58 -0
- data/doc/todo.txt +19 -0
- data/lib/bibliotech/application.rb +95 -0
- data/lib/bibliotech/backups/file_record.rb +16 -0
- data/lib/bibliotech/backups/prune_list.rb +58 -0
- data/lib/bibliotech/backups/pruner.rb +71 -0
- data/lib/bibliotech/backups/scheduler.rb +49 -0
- data/lib/bibliotech/builders/database.rb +25 -0
- data/lib/bibliotech/builders/file.rb +75 -0
- data/lib/bibliotech/builders/gzip.rb +51 -0
- data/lib/bibliotech/builders/mysql.rb +35 -0
- data/lib/bibliotech/builders/postgres.rb +37 -0
- data/lib/bibliotech/builders.rb +43 -0
- data/lib/bibliotech/cli.rb +24 -0
- data/lib/bibliotech/command_generator.rb +86 -0
- data/lib/bibliotech/command_runner.rb +36 -0
- data/lib/bibliotech/compression/bzip2.rb +6 -0
- data/lib/bibliotech/compression/gzip.rb +6 -0
- data/lib/bibliotech/compression/sevenzip.rb +5 -0
- data/lib/bibliotech/compression.rb +35 -0
- data/lib/bibliotech/config.rb +269 -0
- data/lib/bibliotech/rake_lib.rb +82 -0
- data/lib/bibliotech.rb +7 -0
- data/spec/bibliotech/backup_pruner_spec.rb +58 -0
- data/spec/bibliotech/backup_scheduler_spec.rb +108 -0
- data/spec/bibliotech/command_generator/mysql_spec.rb +170 -0
- data/spec/bibliotech/command_generator/postgres_spec.rb +180 -0
- data/spec/bibliotech/command_generator_spec.rb +99 -0
- data/spec/bibliotech/command_runner_spec.rb +50 -0
- data/spec/bibliotech/compression/bunzip2_spec.rb +9 -0
- data/spec/bibliotech/compression/bzip2_spec.rb +9 -0
- data/spec/bibliotech/compression/gzip_spec.rb +9 -0
- data/spec/bibliotech/compression/sevenzip_spec.rb +9 -0
- data/spec/bibliotech/compression_spec.rb +28 -0
- data/spec/bibliotech/config_spec.rb +151 -0
- data/spec/gem_test_suite.rb +0 -0
- data/spec/spec_helper.rb +2 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fa336e5473f11901ab7dcddd786ad077ca6cc9d4
|
4
|
+
data.tar.gz: 7823f13b23335c838867c1c6d20dee7707924c39
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb3d5921a720ab1dc4e2b682d8d6be78155a4f6c39a70554b1c83a164696624fbf7fc3188b865c2a8904e20cacb8a1240a1e407867f13bc6eba7da7a675e013f
|
7
|
+
data.tar.gz: a16f586a9e18301134a6a454a7cd3749b64994753f4b94a8076ca862bbcf82479f2dd83e508fa08c3a0d3264293fccb9d3d11a0e6b5d7ee1c98f3d7796b3f9fb
|
data/bin/bibliotech
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# bibliotech.yml on production server
|
2
|
+
# used by commands like
|
3
|
+
# rake bibliotech:backups:create
|
4
|
+
# rake bibliotech:backups:cycle
|
5
|
+
backups:
|
6
|
+
dir: db_backups
|
7
|
+
compress: gzip # [ none, gzip, bzip2, 7zip ]
|
8
|
+
keep:
|
9
|
+
hourlies: 48
|
10
|
+
dailies: 14
|
11
|
+
weeklies: 8
|
12
|
+
monthlies: all
|
13
|
+
|
14
|
+
create: hourly
|
15
|
+
prune: daily
|
16
|
+
clone:
|
17
|
+
storage: s3 [ file, ftp, scp ]
|
18
|
+
target: ecliptic_db_backups # URL
|
19
|
+
|
20
|
+
database_config_env: production
|
21
|
+
database_config_file: 'config/database.yml' # this is the default
|
22
|
+
# -- or --
|
23
|
+
database_config:
|
24
|
+
hostname: xxxx
|
25
|
+
username: xxxxx
|
26
|
+
password: xxxxx
|
27
|
+
port: xxxx
|
28
|
+
|
29
|
+
#bibliotech.yml on staging server
|
30
|
+
#useful for:
|
31
|
+
# > rake bibliotech:restore_from[<remote name, e.g. production>]
|
32
|
+
# # (copies most recent database backup from this path or URL and restores it)
|
33
|
+
backups: none
|
34
|
+
database_config_env: staging
|
35
|
+
database_config_file: 'config/database.yml' # this is the default
|
36
|
+
# -- or --
|
37
|
+
database_config:
|
38
|
+
hostname: xxxx
|
39
|
+
username: xxxxx
|
40
|
+
password: xxxxx
|
41
|
+
port: xxxx
|
42
|
+
|
43
|
+
remotes:
|
44
|
+
production:
|
45
|
+
host: some.server.com
|
46
|
+
path: "/var/www/eclipticenterprises.com/current"
|
47
|
+
compressed: gzip
|
48
|
+
|
49
|
+
|
50
|
+
#bibliotech.yml on dev
|
51
|
+
# used by commands like
|
52
|
+
# > rake bibliotech:remote_sync:down[production] # production is default if ommitted
|
53
|
+
# > rake bibliotech:remote_sync:up
|
54
|
+
# > cap bibliotech:remote_sync:down[production] # uses Cap config, so URL below is unnecessary in config
|
55
|
+
#
|
56
|
+
remotes:
|
57
|
+
production: "root@appserver2.lrdesign.com:/var/www/eclipticenterprises.com/current"
|
58
|
+
staging: "root@appserver2.lrdesign.com:/var/www/staging.eclipticenterprises.com/current"
|
data/doc/todo.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
* load DB configs from a database.yml
|
2
|
+
* export commands: mysql
|
3
|
+
* export commands: posgresql
|
4
|
+
* export commands: postgis
|
5
|
+
* import commands: mysql
|
6
|
+
* import commands: postgresql
|
7
|
+
* import commands: postgis
|
8
|
+
* rake task: backup databases
|
9
|
+
* rake task: filter dated DB backups
|
10
|
+
* configuration: how many backups to keep of what age
|
11
|
+
*
|
12
|
+
* rake task: import DB locally by filename
|
13
|
+
* rake task: import[&migrate] most recent DB (at default name) from production, locally
|
14
|
+
* rake task: import[&migrate] most recent DB (at default name) from production, remotely
|
15
|
+
* rake task: download and import remote sql backup
|
16
|
+
* rake task: upload and import local backup to remote:
|
17
|
+
*
|
18
|
+
* cap task: download and import remote sql backup
|
19
|
+
* cap task: upload and import local backup to remote:
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'bibliotech'
|
2
|
+
require 'caliph'
|
3
|
+
require 'valise'
|
4
|
+
require 'bibliotech/backups/pruner'
|
5
|
+
|
6
|
+
module BiblioTech
|
7
|
+
class Application
|
8
|
+
attr_accessor :config_path, :config_hash
|
9
|
+
attr_writer :shell
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@memos = {}
|
13
|
+
@shell = Caliph.new
|
14
|
+
@config_path = %w{/etc/bibliotech /usr/share/bibliotech ~/.bibliotech ./.bibliotech ./config/bibliotech}
|
15
|
+
end
|
16
|
+
|
17
|
+
def valise
|
18
|
+
@memos[:valise] ||=
|
19
|
+
begin
|
20
|
+
dirs = config_path
|
21
|
+
Valise::define do
|
22
|
+
dirs.reverse.each do |dir|
|
23
|
+
rw dir
|
24
|
+
end
|
25
|
+
ro from_here(%w{.. default_configuration}, up_to("lib"))
|
26
|
+
handle "*.yaml", :yaml, :hash_merge
|
27
|
+
handle "*.yml", :yaml, :hash_merge
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def config
|
33
|
+
@memos[:config] ||= Config.new(valise)
|
34
|
+
end
|
35
|
+
|
36
|
+
def commands
|
37
|
+
@memos[:command] ||= CommandGenerator.new(config)
|
38
|
+
end
|
39
|
+
|
40
|
+
def pruner(options)
|
41
|
+
Backups::Pruner.new(config.merge(options))
|
42
|
+
end
|
43
|
+
|
44
|
+
def prune_list(options)
|
45
|
+
pruner(options).list
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset
|
49
|
+
@memos.clear
|
50
|
+
end
|
51
|
+
|
52
|
+
def import(options)
|
53
|
+
@shell.run(commands.import(options))
|
54
|
+
end
|
55
|
+
|
56
|
+
def export(options)
|
57
|
+
@shell.run(commands.export(options))
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_backup(options)
|
61
|
+
time = Time.now.utc
|
62
|
+
pruner = pruner(options)
|
63
|
+
return unless pruner.backup_needed?(time)
|
64
|
+
options["backups"] ||= options[:backups] || {}
|
65
|
+
options["backups"]["filename"] = pruner.filename_for(time)
|
66
|
+
export(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
#pull a dump from a remote
|
70
|
+
def get(options)
|
71
|
+
@shell.run(commands.fetch(options))
|
72
|
+
end
|
73
|
+
|
74
|
+
#push a dump to a remote
|
75
|
+
def send(options)
|
76
|
+
@shell.run(commands.push(options))
|
77
|
+
end
|
78
|
+
|
79
|
+
#clean up the DB dumps
|
80
|
+
def prune(options=nil)
|
81
|
+
pruner(option || {}).go
|
82
|
+
end
|
83
|
+
|
84
|
+
#return the latest dump of the DB
|
85
|
+
def latest(options = nil)
|
86
|
+
pruner(options || {}).most_recent.path
|
87
|
+
end
|
88
|
+
|
89
|
+
def remote_cli(remote, command, options)
|
90
|
+
@shell.run(commands.ssh_cli(remote, command, options))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
App = Application
|
95
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module BiblioTech
|
2
|
+
module Backups
|
3
|
+
class PruneList
|
4
|
+
TIMESTAMP_REGEX = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})_(?<hour>\d{2}):(?<minute>\d{2})/
|
5
|
+
|
6
|
+
attr_accessor :path, :prefix
|
7
|
+
|
8
|
+
def initialize(path, prefix)
|
9
|
+
@path, @prefix = path, prefix
|
10
|
+
end
|
11
|
+
|
12
|
+
def list
|
13
|
+
files = []
|
14
|
+
Dir.new(path).each do |file|
|
15
|
+
next if %w{. ..}.include?(file)
|
16
|
+
file_record = build_record(file)
|
17
|
+
if file_record.nil?
|
18
|
+
else
|
19
|
+
files << file_record
|
20
|
+
end
|
21
|
+
end
|
22
|
+
files
|
23
|
+
end
|
24
|
+
|
25
|
+
def prefix_timestamp_re
|
26
|
+
prefix_re(TIMESTAMP_REGEX)
|
27
|
+
end
|
28
|
+
|
29
|
+
def prefix_re(also)
|
30
|
+
/\A#{prefix}-#{also}\..*\z/
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.filename_for(prefix, time)
|
34
|
+
time.strftime("#{prefix}-%Y-%m-%d_%H:%M.sql")
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_record(file)
|
38
|
+
if file =~ prefix_re(/.*/)
|
39
|
+
if !(match = prefix_timestamp_re.match(file)).nil?
|
40
|
+
timespec = %w{year month day hour minute}.map do |part|
|
41
|
+
Integer(match[part], 10)
|
42
|
+
end
|
43
|
+
parsed_time = Time::utc(*timespec)
|
44
|
+
return FileRecord.new(File::join(path, file), parsed_time)
|
45
|
+
else
|
46
|
+
raise "File prefixed #{prefix} doesn't match #{prefix_timestamp_re.to_s}: #{File::join(path, file)}"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
if file !~ TIMESTAMP_REGEX
|
50
|
+
warn "Stray file in backups directory: #{File::join(path, file)}"
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'bibliotech/backups/prune_list'
|
2
|
+
require 'bibliotech/backups/file_record'
|
3
|
+
require 'bibliotech/backups/scheduler'
|
4
|
+
|
5
|
+
module BiblioTech
|
6
|
+
module Backups
|
7
|
+
class Pruner
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def path
|
14
|
+
@path ||= config.backup_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name ||= config.backup_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def schedules
|
22
|
+
@schedules ||=
|
23
|
+
[].tap do |array|
|
24
|
+
config.each_prune_schedule do |frequency, limit|
|
25
|
+
array << Scheduler.new(frequency, limit)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def backup_needed?(time)
|
31
|
+
time - most_recent.timestamp < config.backup_frequency * 60
|
32
|
+
end
|
33
|
+
|
34
|
+
def list
|
35
|
+
@list ||=
|
36
|
+
begin
|
37
|
+
list = PruneList.new(path, name).list
|
38
|
+
schedules.each do |schedule|
|
39
|
+
schedule.mark(list)
|
40
|
+
end
|
41
|
+
list
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def most_recent
|
46
|
+
list.max_by do |record|
|
47
|
+
record.timestamp
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def filename_for(time)
|
52
|
+
PruneList.filename_for(time)
|
53
|
+
end
|
54
|
+
|
55
|
+
def pruneable
|
56
|
+
list.select do |record|
|
57
|
+
!record.keep?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def go
|
62
|
+
return if schedules.empty?
|
63
|
+
pruneable.each {|record| delete(record.path) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete(path)
|
67
|
+
File.unlink(path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'bibliotech/backups/file_record'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Backups
|
5
|
+
class Scheduler
|
6
|
+
attr_accessor :frequency, :limit
|
7
|
+
|
8
|
+
def initialize(frequency, limit)
|
9
|
+
@frequency, @limit = frequency, limit
|
10
|
+
@limit = nil if limit == "all"
|
11
|
+
end
|
12
|
+
|
13
|
+
def end_time(file_list)
|
14
|
+
file_list.map{|record| record.timestamp}.max
|
15
|
+
end
|
16
|
+
|
17
|
+
def compute_start_time(file_list)
|
18
|
+
limit_time = Time.at(0)
|
19
|
+
unless limit.nil?
|
20
|
+
limit_time = end_time(file_list) - limit * freq_seconds
|
21
|
+
end
|
22
|
+
[limit_time, file_list.map{|record| record.timestamp}.min - range].max
|
23
|
+
end
|
24
|
+
|
25
|
+
def freq_seconds
|
26
|
+
frequency * 60
|
27
|
+
end
|
28
|
+
|
29
|
+
def range
|
30
|
+
freq_seconds / 2
|
31
|
+
end
|
32
|
+
|
33
|
+
def mark(file_list)
|
34
|
+
time = end_time(file_list)
|
35
|
+
start_time = compute_start_time(file_list)
|
36
|
+
while time > start_time do
|
37
|
+
closest = file_list.min_by do |record|
|
38
|
+
(record.timestamp - time).abs
|
39
|
+
end
|
40
|
+
if (closest.timestamp - time).abs < range
|
41
|
+
closest.keep = true
|
42
|
+
end
|
43
|
+
time -= freq_seconds
|
44
|
+
end
|
45
|
+
return file_list
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'bibliotech/builders'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Builders
|
5
|
+
class Database < Base
|
6
|
+
def self.find_class(config)
|
7
|
+
adapter_registry.fetch(config.adapter) do
|
8
|
+
raise "config.adapter is #{config.adapter.inspect} - supported adapters are #{supported_adapters.join(", ")}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Import < Database
|
14
|
+
def self.registry_host
|
15
|
+
Import
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Export < Database
|
20
|
+
def self.registry_host
|
21
|
+
Export
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'bibliotech/builders'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Builders
|
5
|
+
class File < Base
|
6
|
+
def self.find_class(config)
|
7
|
+
file = config.backup_file
|
8
|
+
|
9
|
+
explicit = find_explicit(config)
|
10
|
+
return explicit unless explicit.nil?
|
11
|
+
|
12
|
+
_, klass = adapter_registry.find{ |pattern, klass|
|
13
|
+
next if pattern.is_a? Symbol
|
14
|
+
file =~ pattern
|
15
|
+
}
|
16
|
+
klass || identity_adapter
|
17
|
+
rescue Config::MissingConfig
|
18
|
+
return NullAdapter
|
19
|
+
end
|
20
|
+
|
21
|
+
def file
|
22
|
+
config.backup_file
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class FileInput < File
|
27
|
+
def self.identity_adapter
|
28
|
+
IdentityFileInput
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.find_explicit(config)
|
32
|
+
return adapter_registry.fetch(config.expander) do
|
33
|
+
raise "config.expander is #{config.expander.inspect} - supported expanders are #{supported_adapters.select{|ad| ad.is_a? Symbol}.join(", ")}"
|
34
|
+
end
|
35
|
+
rescue Config::MissingConfig
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.registry_host
|
40
|
+
FileInput
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FileOutput < File
|
45
|
+
def self.identity_adapter
|
46
|
+
IdentityFileOutput
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find_explicit(config)
|
50
|
+
return adapter_registry.fetch(config.compressor) do
|
51
|
+
raise "config.compressor is #{config.compressor.inspect} - supported compressors are #{supported_adapters.select{|ad| ad.is_a? Symbol}.join(", ")}"
|
52
|
+
end
|
53
|
+
rescue KeyError
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.registry_host
|
57
|
+
FileOutput
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class IdentityFileInput < FileInput
|
62
|
+
def go(cmd)
|
63
|
+
cmd.redirect_stdin(file)
|
64
|
+
cmd
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class IdentityFileOutput < FileOutput
|
69
|
+
def go(cmd)
|
70
|
+
cmd.redirect_stdout(file)
|
71
|
+
cmd
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'bibliotech/builders/file'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Builders
|
5
|
+
class GzipExpander < FileInput
|
6
|
+
register(/.*\.gz\z/)
|
7
|
+
register(/.*\.gzip\z/)
|
8
|
+
|
9
|
+
def go(command)
|
10
|
+
command = cmd("gunzip", file) | command
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ExplicitGzipExpander < GzipExpander
|
15
|
+
register :gzip
|
16
|
+
|
17
|
+
def file
|
18
|
+
file = super
|
19
|
+
unless PATTERNS.any?{|pattern| pattern =~ file}
|
20
|
+
return file + ".gz"
|
21
|
+
end
|
22
|
+
file
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class GzipCompressor < FileOutput
|
27
|
+
PATTERNS = [ /.*\.gz\z/, /.*\.gzip\z/ ]
|
28
|
+
PATTERNS.each do |pattern|
|
29
|
+
register pattern
|
30
|
+
end
|
31
|
+
|
32
|
+
def go(cmd)
|
33
|
+
cmd |= %w{gzip}
|
34
|
+
cmd.redirect_stdout(file)
|
35
|
+
cmd
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ExplicitGzipCompressor < GzipCompressor
|
40
|
+
register :gzip
|
41
|
+
|
42
|
+
def file
|
43
|
+
file = super
|
44
|
+
unless PATTERNS.any?{|pattern| pattern =~ file}
|
45
|
+
return file + ".gz"
|
46
|
+
end
|
47
|
+
file
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bibliotech/builders/database'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Builders
|
5
|
+
module MySql
|
6
|
+
class Export < Builders::Export
|
7
|
+
register :mysql
|
8
|
+
|
9
|
+
def go(command)
|
10
|
+
command.from('mysqldump')
|
11
|
+
config.optional{ command.options << "-h #{config.host}" }
|
12
|
+
config.optional{ command.options << "-u #{config.username}" }
|
13
|
+
config.optional{ command.options << "-P #{config.port}" } #ok
|
14
|
+
config.optional{ command.options << "--password='#{config.password}'" }
|
15
|
+
command.options << "#{config.database}"
|
16
|
+
command
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Import < Builders::Import
|
21
|
+
register :mysql
|
22
|
+
|
23
|
+
def go(command)
|
24
|
+
command.from('mysql')
|
25
|
+
config.optional{ command.options << "-h #{config.host}" }
|
26
|
+
config.optional{ command.options << "-u #{config.username}" }
|
27
|
+
config.optional{ command.options << "-P #{config.port}" } #ok
|
28
|
+
config.optional{ command.options << "--password='#{config.password}'" }
|
29
|
+
command.options << "#{config.database}"
|
30
|
+
command
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'bibliotech/builders/database'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
module Builders
|
5
|
+
module Postgres
|
6
|
+
class Export < Builders::Export
|
7
|
+
register :postgres
|
8
|
+
|
9
|
+
def go(command)
|
10
|
+
command.from('pg_dump', '-Fc')
|
11
|
+
config.optional{ command.options << "-h #{config.host}" }
|
12
|
+
config.optional{ command.env["PGPASSWORD"] = config.password }
|
13
|
+
config.optional{ command.options << "-p #{config.port}" } #ok
|
14
|
+
|
15
|
+
command.options << "-U #{config.username}"
|
16
|
+
command.options << "#{config.database}"
|
17
|
+
command
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Import < Builders::Import
|
22
|
+
register :postgres
|
23
|
+
|
24
|
+
def go(command)
|
25
|
+
command.from('pg_restore')
|
26
|
+
config.optional{ command.options << "-h #{config.host}" }
|
27
|
+
config.optional{ command.env["PGPASSWORD"] = config.password }
|
28
|
+
config.optional{ command.options << "-p #{config.port}" } #ok
|
29
|
+
|
30
|
+
command.options << "-U #{config.username}"
|
31
|
+
command.options << "-d #{config.database}"
|
32
|
+
command
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module BiblioTech
|
2
|
+
module Builders
|
3
|
+
class Base
|
4
|
+
include Caliph::CommandLineDSL
|
5
|
+
class << self
|
6
|
+
def register(adapter_name)
|
7
|
+
adapter_registry[adapter_name] = self
|
8
|
+
end
|
9
|
+
|
10
|
+
def adapter_registry
|
11
|
+
registry_host.registry
|
12
|
+
end
|
13
|
+
|
14
|
+
def registry
|
15
|
+
@registry ||={}
|
16
|
+
end
|
17
|
+
|
18
|
+
def supported_adapters
|
19
|
+
adapter_registry.keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def for(config)
|
23
|
+
find_class(config).new(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def null_adapter
|
27
|
+
NullAdapter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(config)
|
32
|
+
@config = config
|
33
|
+
end
|
34
|
+
attr_reader :config
|
35
|
+
end
|
36
|
+
|
37
|
+
class NullAdapter < Base
|
38
|
+
def go(cmd)
|
39
|
+
cmd
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'bibliotech/application'
|
3
|
+
|
4
|
+
module BiblioTech
|
5
|
+
class CLI < Thor
|
6
|
+
desc "latest", "Outputs the latest DB dump available locally"
|
7
|
+
def latest
|
8
|
+
app = App.new
|
9
|
+
app.latest
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "dump FILENAME", "Create a new database dump into FILE"
|
13
|
+
def dump(file)
|
14
|
+
app = App.new
|
15
|
+
app.export(:backups => { :filename => file })
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "load FILENAME", "Load a database file from FILE"
|
19
|
+
def load(file)
|
20
|
+
app = App.new
|
21
|
+
app.import(:backups => { :filename => file })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|