bibliotech 0.2.13 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a22ff69ff51f9da65c676de7b84084129f2a9499
4
- data.tar.gz: 414d7b654e61ab08ac35e26c3ecebb161761c4dd
3
+ metadata.gz: 07e130f15c7d64d6bd751f7663a913adb30a5551
4
+ data.tar.gz: efc5d69b6b40c211c0959020bfaf7ab46044ec56
5
5
  SHA512:
6
- metadata.gz: ee5d38a9f9a2a393e9b44d07f15dba07e8285cad1b5a931ad5de2b3ca955e13b15962aec59cc76c699fd1aa494c10460e5ce1a9d26e4ac3e6085fdc6d4cfe8d7
7
- data.tar.gz: 490311e700290021309b7991116fb11bc79db31be92b53c87144a355c7527ad3139ff41994f3c72a872bec96c7060998d1cb47c91668531795fdfc661564512b
6
+ metadata.gz: 95ac9c114a022bbbda10710e62a4f9191aa242f7f9c4b68aa8fd9576424c65309e168bbc5b0ed0e5727477af76be81017cd71ceeca87e2c25cd1488d57e0a2b3
7
+ data.tar.gz: 88626b4a157c11e5d4294bfd3aa8a7b730db4f685c5e755a83f87935e27cd42a37b7fc00a44a95be0342489689e68278411b73ccd68d24d17c46d21d0f951a83
@@ -7,6 +7,9 @@ backups:
7
7
  dir: db_backups
8
8
  compress: gzip # [ none, gzip, bzip2, 7zip ]
9
9
  prefix: backup
10
+ log:
11
+ target: stderr
12
+ level: debug
10
13
 
11
14
  production:
12
15
  backups:
@@ -2,9 +2,12 @@ require 'bibliotech'
2
2
  require 'caliph'
3
3
  require 'valise'
4
4
  require 'bibliotech/backups/pruner'
5
+ require 'bibliotech/logger'
5
6
 
6
7
  module BiblioTech
7
8
  class Application
9
+ include Logging
10
+
8
11
  attr_accessor :config_path, :config_hash
9
12
  attr_writer :shell
10
13
 
@@ -30,7 +33,19 @@ module BiblioTech
30
33
  end
31
34
 
32
35
  def config
33
- @memos[:config] ||= Config.new(valise)
36
+ @memos[:config] ||=
37
+ begin
38
+ Config.new(valise).tap do |config|
39
+ setup_logger(config)
40
+ end
41
+ end
42
+ end
43
+
44
+ def setup_logger(config)
45
+ logger = Logger.new(config.log_target)
46
+ logger.level = config.log_level
47
+ BiblioTech::Logging.logger = logger
48
+ log.info("Started logging")
34
49
  end
35
50
 
36
51
  def commands
@@ -59,6 +74,7 @@ module BiblioTech
59
74
 
60
75
  def create_backup(options)
61
76
  time = Time.now.utc
77
+ log.warn{ "Creating a backup at #{time}" }
62
78
  pruner = pruner(options)
63
79
  return unless pruner.backup_needed?(time)
64
80
  options["backups"] ||= options[:backups] || {}
@@ -68,25 +84,32 @@ module BiblioTech
68
84
 
69
85
  #pull a dump from a remote
70
86
  def get(remote, options)
87
+ log.warn{ "Getting a dump from #{remote}" }
71
88
  @shell.run(commands.fetch(remote, options))
72
89
  end
73
90
 
74
91
  #push a dump to a remote
75
92
  def send(remote, options)
93
+ log.warn{ "Sending a dump to #{remote}" }
76
94
  @shell.run(commands.push(remote, options))
77
95
  end
78
96
 
79
97
  #clean up the DB dumps
80
98
  def prune(options=nil)
99
+ log.warn{ "Pruning DB records" }
81
100
  pruner(options || {}).go
82
101
  end
83
102
 
84
103
  #return the latest dump of the DB
85
104
  def latest(options = nil)
86
- pruner(options || {}).most_recent.path
105
+ log.info{ "Getting most recent DB dump" }
106
+ pruner(options || {}).most_recent.path.tap do |latest|
107
+ log.info{ " #{latest}" }
108
+ end
87
109
  end
88
110
 
89
111
  def remote_cli(remote, command, *options)
112
+ log.warn{ "Running #{command} on #{remote}" }
90
113
  @shell.run(commands.remote_cli(remote, command, *options))
91
114
  end
92
115
  end
@@ -1,16 +1,22 @@
1
1
  module BiblioTech
2
2
  module Backups
3
3
  class FileRecord
4
- attr_accessor :path, :timestamp, :keep
4
+ attr_accessor :path, :timestamp, :keep, :scheduled_by
5
5
 
6
6
  def initialize(path, timestamp)
7
7
  @path, @timestamp = path, timestamp
8
8
  @keep = false
9
+ @scheduled_by = []
9
10
  end
10
11
 
11
12
  def keep?
12
13
  !!@keep
13
14
  end
15
+
16
+ def in_schedule(name)
17
+ @scheduled_by << name
18
+ @keep = true
19
+ end
14
20
  end
15
21
  end
16
22
  end
@@ -19,7 +19,9 @@ module BiblioTech
19
19
  files << file_record
20
20
  end
21
21
  end
22
- files
22
+ files.sort_by do |record|
23
+ - record.timestamp.to_i
24
+ end
23
25
  end
24
26
 
25
27
  def prefix_timestamp_re
@@ -1,10 +1,11 @@
1
1
  require 'bibliotech/backups/prune_list'
2
2
  require 'bibliotech/backups/file_record'
3
- require 'bibliotech/backups/scheduler'
3
+ require 'bibliotech/logger'
4
4
 
5
5
  module BiblioTech
6
6
  module Backups
7
7
  class Pruner
8
+ include Logging
8
9
  def initialize(config)
9
10
  @config = config
10
11
  end
@@ -19,18 +20,17 @@ module BiblioTech
19
20
  end
20
21
 
21
22
  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
23
+ @schedules ||= config.prune_schedules
24
+ end
25
+
26
+ def frequency
27
+ @frequency ||= config.backup_frequency
28
28
  end
29
29
 
30
30
  def backup_needed?(time)
31
31
  most_recent = most_recent()
32
32
  return true if most_recent.nil?
33
- (time - most_recent.timestamp) > (config.backup_frequency * 60)
33
+ (time - most_recent.timestamp) > (frequency * 60)
34
34
  end
35
35
 
36
36
  def list
@@ -50,11 +50,19 @@ module BiblioTech
50
50
  end
51
51
 
52
52
  def filename_for(time)
53
- PruneList.filename_for(config.backup_name, time)
53
+ PruneList.filename_for(name, time)
54
54
  end
55
55
 
56
56
  def pruneable
57
57
  mark_list
58
+ if list.empty?
59
+ log.warn{ "No backup files in #{path} / #{name} !" }
60
+ end
61
+ list.each do |record|
62
+ log.info{
63
+ "#{record.path} #{record.timestamp} #{record.keep ? "kept: #{record.scheduled_by.inspect}" : "discarding"}"
64
+ }
65
+ end
58
66
  list.select do |record|
59
67
  !record.keep?
60
68
  end
@@ -3,9 +3,10 @@ require 'bibliotech/backups/file_record'
3
3
  module BiblioTech
4
4
  module Backups
5
5
  class Scheduler
6
- attr_accessor :frequency, :limit
6
+ attr_accessor :frequency, :limit, :name
7
7
 
8
- def initialize(frequency, limit)
8
+ def initialize(name, frequency, limit)
9
+ @name = name
9
10
  @frequency, @limit = frequency, limit
10
11
  @limit = nil if limit == "all"
11
12
  end
@@ -51,7 +52,7 @@ module BiblioTech
51
52
  closest = file_list.first
52
53
 
53
54
  if (time - closest.timestamp) < freq_seconds
54
- closest.keep = true
55
+ closest.in_schedule(name)
55
56
  end
56
57
  time -= freq_seconds
57
58
  end
@@ -3,11 +3,13 @@ require 'caliph'
3
3
  require 'bibliotech/builders/gzip'
4
4
  require 'bibliotech/builders/postgres'
5
5
  require 'bibliotech/builders/mysql'
6
+ require 'bibliotech/logger'
6
7
 
7
8
  module BiblioTech
8
9
  class CommandGenerator
9
10
 
10
11
  include Caliph::CommandLineDSL
12
+ include Logging
11
13
 
12
14
  attr_accessor :config
13
15
 
@@ -19,14 +21,18 @@ module BiblioTech
19
21
  options = config.merge(options || {})
20
22
  command = cmd
21
23
  command = Builders::Export.for(options).go(command)
22
- Builders::FileOutput.for(options).go(command)
24
+ Builders::FileOutput.for(options).go(command).tap do |cmd|
25
+ log.info{ cmd.command }
26
+ end
23
27
  end
24
28
 
25
29
  def import(options = nil)
26
30
  options = config.merge(options || {})
27
31
  command = cmd()
28
32
  command = Builders::Import.for(options).go(command)
29
- Builders::FileInput.for(options).go(command)
33
+ Builders::FileInput.for(options).go(command).tap do |cmd|
34
+ log.info{ cmd.command }
35
+ end
30
36
  end
31
37
 
32
38
  def fetch(remote, filename, options = nil)
@@ -39,6 +45,8 @@ module BiblioTech
39
45
  options.optionally{ cmd.options << "-i #{options.id_file(remote)}" }
40
46
  cmd.options << options.remote_file(remote, filename)
41
47
  cmd.options << local_path
48
+ end.tap do |cmd|
49
+ log.info{ cmd.command }
42
50
  end
43
51
  end
44
52
 
@@ -47,6 +55,8 @@ module BiblioTech
47
55
  cmd("scp") do |cmd|
48
56
  cmd.options << options.local_file(filename)
49
57
  cmd.options << options.remote_file(remote, filename)
58
+ end.tap do |cmd|
59
+ log.info{ cmd.command }
50
60
  end
51
61
  end
52
62
 
@@ -74,7 +84,9 @@ module BiblioTech
74
84
  cmd.options << "-o #{opt}"
75
85
  end
76
86
  end
77
- end - escaped_command(command_on_remote)
87
+ end - escaped_command(command_on_remote).tap do |cmd|
88
+ log.info{ cmd.command }
89
+ end
78
90
  end
79
91
 
80
92
  def wipe()
@@ -1,3 +1,5 @@
1
+ require 'bibliotech/backups/scheduler'
2
+
1
3
  module BiblioTech
2
4
  class Config
3
5
  class MissingConfig < KeyError; end
@@ -12,6 +14,8 @@ module BiblioTech
12
14
  :rsa_files => [ "rsa_files" ] ,
13
15
  :ssh_options => [ "ssh_options" ] ,
14
16
  :fetch_dir => [ "fetched_dir" ] ,
17
+ :log_target => [ "log" , "target" ],
18
+ :log_level => [ "log" , "level" ],
15
19
  :file => [ "backups" , "file" ] ,
16
20
  :filename => [ "backups" , "filename" ] ,
17
21
  :backup_path => [ "backups" , "dir" ] ,
@@ -113,6 +117,32 @@ module BiblioTech
113
117
  extract(steps, ["remotes"] + steps)
114
118
  end
115
119
 
120
+ def log_target
121
+ target_path = local_get(:log_target)
122
+ case target_path
123
+ when "STDERR", "stderr"
124
+ return $stderr
125
+ when "STDOUT", "stdout"
126
+ return $stdout
127
+ else
128
+ require 'fileutils'
129
+ FileUtils.mkdir_p(File.dirname(target_path))
130
+ return File.open(target_path, "a")
131
+ end
132
+ rescue
133
+ warn "Trouble opening configured log file - logging to stderr"
134
+ warn $!.inspect
135
+ return $STDERR
136
+ end
137
+
138
+ def log_level
139
+ level = "debug"
140
+ optionally do
141
+ level = local_get(:log_level)
142
+ end
143
+ return BiblioTech::Logging.log_level(level)
144
+ end
145
+
116
146
  def ssh_options(for_remote)
117
147
  steps = steps_for(:ssh_options) + [for_remote]
118
148
  steps_chain =
@@ -202,7 +232,7 @@ module BiblioTech
202
232
  @backup_frequency ||= regularize_frequency(local_get(:backup_frequency))
203
233
  end
204
234
 
205
- def each_prune_schedule
235
+ def prune_schedules
206
236
  local_get(:prune_schedule).map do |frequency, limit|
207
237
  real_frequency = regularize_frequency(frequency)
208
238
  unless real_frequency % backup_frequency == 0
@@ -215,11 +245,11 @@ module BiblioTech
215
245
  else
216
246
  Integer(limit)
217
247
  end
218
- [real_frequency, limit]
219
- end.sort_by do |frequency, limit|
248
+ [frequency, real_frequency, limit]
249
+ end.sort_by do |freq_name, frequency, limit|
220
250
  frequency
221
- end.each do |frequency, limit|
222
- yield(frequency, limit)
251
+ end.map do |freq_name, frequency, limit|
252
+ Backups::Scheduler.new(freq_name, frequency, limit)
223
253
  end
224
254
  end
225
255
 
@@ -0,0 +1,36 @@
1
+ require 'logger'
2
+
3
+ module BiblioTech
4
+ module Logging
5
+ def self.logger
6
+ return @logger
7
+ end
8
+
9
+ def self.logger=(value)
10
+ @logger = value
11
+ end
12
+
13
+ def log
14
+ return BiblioTech::Logging.logger
15
+ end
16
+ module_function :log
17
+
18
+ def self.log_level(string)
19
+ case string
20
+ when /fatal/i
21
+ Logger::FATAL
22
+ when /error/i
23
+ Logger::ERROR
24
+ when /warn/i
25
+ Logger::WARN
26
+ when /info/i
27
+ Logger::INFO
28
+ when /debug/i
29
+ Logger::DEBUG
30
+ else
31
+ Logger::DEBUG
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -1,5 +1,6 @@
1
1
  require 'bibliotech/application'
2
2
  require 'bibliotech/backups/pruner'
3
+ require 'bibliotech/backups/scheduler'
3
4
  require 'file-sandbox'
4
5
  module BiblioTech
5
6
  describe Backups::Pruner do
@@ -7,6 +8,7 @@ module BiblioTech
7
8
 
8
9
  before :each do
9
10
  sandbox.new :directory => "db_backups"
11
+ sandbox.new :file => '.bibliotech/config.yaml', :with_contents => "log:\n target: ../tmp/test.log"
10
12
  end
11
13
 
12
14
  let :app do
@@ -17,13 +19,17 @@ module BiblioTech
17
19
  {:daily => 100}
18
20
  end
19
21
 
22
+ let :config do
23
+ double(Config).tap do |config|
24
+ allow(config).to receive(:backup_path){ "db_backups" }
25
+ allow(config).to receive(:backup_name){ "testing" }
26
+ allow(config).to receive(:backup_frequency){ 60 * 24 }
27
+ allow(config).to receive(:schedules){ [ Backup::Scheduler.new("daily", 60 * 24, 100) ] }
28
+ end
29
+ end
30
+
20
31
  let :pruner do
21
- app.pruner({:backups => {
22
- :frequency => "daily",
23
- :prefix => "testing",
24
- :keep => schedule,
25
- :dir => "db_backups"
26
- }})
32
+ Backups::Pruner.new(config)
27
33
  end
28
34
 
29
35
  it "should generate a filename for current time" do
@@ -65,6 +71,115 @@ module BiblioTech
65
71
  expect(pruner.backup_needed?(Time.now.utc)).to be_truthy
66
72
  end
67
73
  end
74
+
75
+ context "marking for pruning" do
76
+ before :each do
77
+ allow(config).to receive(:prune_schedules){
78
+ [
79
+ Backups::Scheduler.new("hourlies", 60, 48),
80
+ Backups::Scheduler.new("dailies", 24 * 60, 14),
81
+ Backups::Scheduler.new("weeklies", 7 * 24 * 60, 8),
82
+ Backups::Scheduler.new("monthlies", 30 * 24 * 60, nil)
83
+ ]
84
+ }
85
+
86
+ Logging.log.debug{ "Start test" }
87
+ end
88
+
89
+ it "should have schedules" do
90
+ expect(pruner.schedules.length).to eq(4)
91
+ end
92
+
93
+ it "should keep single backup" do
94
+ sandbox.new :file => "db_backups/#{pruner.filename_for(Time.now.utc)}"
95
+
96
+ expect(pruner.pruneable).to be_empty
97
+
98
+ expect(pruner.list.length).to eq(1)
99
+ pruner.list.each do |record|
100
+ expect(record.keep?).to eq(true)
101
+ end
102
+ end
103
+
104
+ it "should keep 48 hours of backup" do
105
+ now = Time.now.utc
106
+ (0..47).each do |interval|
107
+ sandbox.new :file => "db_backups/#{pruner.filename_for(Time.now.utc - interval * 60 * 60)}"
108
+ end
109
+
110
+ expect(pruner.pruneable).to be_empty
111
+
112
+ expect(pruner.list.length).to eq(48)
113
+ pruner.list.each do |record|
114
+ expect(record.keep?).to eq(true)
115
+ end
116
+ end
117
+
118
+ it "should prune old backups" do
119
+ now = Time.now.utc
120
+ (0..470).each do |interval|
121
+ sandbox.new :file => "db_backups/#{pruner.filename_for(now - interval * 60 * 60)}"
122
+ end
123
+
124
+ expect(pruner.pruneable.length).to eq(471 - 48 - (14 - 2) - 1) # 2 dailies hourly etc.
125
+
126
+ expect(pruner.list.length).to eq(471)
127
+ end
128
+
129
+
130
+ context "repruning" do
131
+ shared_examples_for "well mannered pruner" do
132
+ it "should not re-prune old backups" do
133
+ (0..47).each do |interval|
134
+ sandbox.new :file => "db_backups/#{pruner.filename_for(now - interval * 60 * 60)}"
135
+ end
136
+ (0..11).each do |interval|
137
+ sandbox.new :file => "db_backups/#{pruner.filename_for(now - 48 * 60 * 60 - interval * 24 * 60 * 60)}"
138
+ end
139
+ sandbox.new :file => "db_backups/#{pruner.filename_for(now - 48 * 60 * 60 - 12 * 24 * 60 * 60 - 7 * 24 * 60 * 60)}"
140
+
141
+ expect(pruner.pruneable).to be_empty
142
+
143
+ expect(pruner.list.length).to eq(48 + 12 + 1)
144
+ pruner.list.each do |record|
145
+ expect(record.keep?).to eq(true)
146
+ end
147
+ end
148
+ end
149
+
150
+ context "right now" do
151
+ it_behaves_like "well mannered pruner" do
152
+ let :now do
153
+ Time.now.utc
154
+ end
155
+ end
156
+ end
157
+
158
+ context "30 minutes ago" do
159
+ it_behaves_like "well mannered pruner" do
160
+ let :now do
161
+ Time.now.utc - 30 * 60
162
+ end
163
+ end
164
+ end
165
+
166
+ context "60 minutes ago" do
167
+ it_behaves_like "well mannered pruner" do
168
+ let :now do
169
+ Time.now.utc - 60 * 60
170
+ end
171
+ end
172
+ end
173
+
174
+ context "90 minutes ago" do
175
+ it_behaves_like "well mannered pruner" do
176
+ let :now do
177
+ Time.now.utc - 90 * 60
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
68
183
  end
69
184
 
70
185
  describe Backups::PruneList do
@@ -23,7 +23,7 @@ module BiblioTech::Backups
23
23
 
24
24
  describe "without a limit" do
25
25
  let :scheduler do
26
- Scheduler.new(60, nil)
26
+ Scheduler.new("hourly", 60, nil)
27
27
  end
28
28
 
29
29
  context "when there's more than enough backups" do
@@ -39,7 +39,7 @@ module BiblioTech::Backups
39
39
 
40
40
  describe "with a limit" do
41
41
  let :scheduler do
42
- Scheduler.new(60, 8)
42
+ Scheduler.new("hourly", 60, 8)
43
43
  end
44
44
 
45
45
  context "when there's just enough backups" do
@@ -89,10 +89,8 @@ module BiblioTech
89
89
  end
90
90
 
91
91
  let :schedule_array do
92
- [].tap do |array|
93
- config.each_prune_schedule do |freq, lim|
94
- array << [freq, lim]
95
- end
92
+ config.prune_schedules.map do |sched|
93
+ [sched.frequency, sched.limit]
96
94
  end
97
95
  end
98
96
 
data/spec/spec_helper.rb CHANGED
@@ -1,2 +1,6 @@
1
1
  require 'rspec'
2
2
  require File.join(File.dirname(__FILE__), '..', 'lib', 'bibliotech')
3
+
4
+ File.open('tmp/test.log', "w") do |logfile|
5
+ logfile.truncate(0)
6
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bibliotech
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Dorn
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-04 00:00:00.000000000 Z
12
+ date: 2014-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caliph
@@ -87,6 +87,7 @@ files:
87
87
  - lib/bibliotech/builders/postgres.rb
88
88
  - lib/bibliotech/builders/mysql.rb
89
89
  - lib/bibliotech/config.rb
90
+ - lib/bibliotech/logger.rb
90
91
  - lib/bibliotech/command_runner.rb
91
92
  - lib/bibliotech/command_generator.rb
92
93
  - lib/bibliotech/rake_lib.rb
@@ -128,7 +129,7 @@ rdoc_options:
128
129
  - --main
129
130
  - doc/README
130
131
  - --title
131
- - bibliotech-0.2.13 Documentation
132
+ - bibliotech-0.3.0 Documentation
132
133
  require_paths:
133
134
  - lib/
134
135
  required_ruby_version: !ruby/object:Gem::Requirement