bibliotech 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|