bibliotech 0.2.13 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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