bibliotech 0.7.1 → 0.8.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: e4a7f80a171d459f3f2cde01453860cd0a0e5750
4
- data.tar.gz: 548aa897f78cef447a4055fca22dd1b9291ef70b
3
+ metadata.gz: edbcdd1d8fee2ac6fbfbf3c9dcb2d95e6e8ab8dd
4
+ data.tar.gz: fe1f5a3328dc2b83d7ba317167d4d5dcc56fae71
5
5
  SHA512:
6
- metadata.gz: 6bbd899f0413760a009a0c394dbef9311c55ed981c75983614b58fc54f4838c3aab03666b61e223e08b4c25a14e3ee8e3288161ab8f332ac198a9cc9fa70fd22
7
- data.tar.gz: 40325bbf4b28acf7ba568cea7467eb7934641b0db74c077e61d0e354aea5b7c90fc1900ec6828f72985515a8e9f7adf617a965a2311e37506283bf8b7df97d33
6
+ metadata.gz: 166cda03abf645d429194b382988be7bfac95e03b96182de7711a3e96891d28b372122971634dd356dc31423baf65886b4b129f7b66c86960352e21d88e83b28
7
+ data.tar.gz: 37e1d3bb553b164f15e9a8143b58f2e3884a8f9f937a40c7a7c2e35088b70550cf8d64c5482ab1202d204a0aaf27a532ba1a86a74c70bb9bb98ac56749f2769d
@@ -7,21 +7,25 @@ backups:
7
7
  dir: db_backups
8
8
  compress: gzip # [ none, gzip, bzip2, 7zip ]
9
9
  prefix: backup
10
- keep:
11
- hourlies: 1
10
+ retain:
11
+ periodic:
12
+ hourlies: 1
12
13
  frequency: hourly
13
14
 
14
15
  log:
15
- target: stderr
16
+ target: log/backups.log
16
17
  level: warn
17
18
 
18
19
  production:
19
20
  backups:
20
- keep:
21
- hourlies: 48
22
- dailies: 14
23
- weeklies: 8
24
- monthlies: all
21
+ retain:
22
+ periodic:
23
+ hourlies: 48
24
+ dailies: 14
25
+ weeklies: 8
26
+ calendar:
27
+ monthlies: 12
28
+ quarterly: all
25
29
 
26
30
  database_config_env: production
27
31
 
@@ -38,6 +42,8 @@ staging:
38
42
  host: some.server.com
39
43
 
40
44
  development:
45
+ log:
46
+ target: stderr
41
47
  database_config_env: development
42
48
  path: "."
43
49
  rsa_files:
@@ -0,0 +1,91 @@
1
+ require 'bibliotech/backups/scheduler'
2
+
3
+ module BiblioTech
4
+ module Backups
5
+ class CalendarScheduler < Scheduler
6
+ # pattern is an array with minutes rightmost
7
+ def initialize(pattern, limit)
8
+ @pattern = pattern
9
+ super(name_for(pattern), freq_for(pattern), limit)
10
+ end
11
+ attr_accessor :pattern
12
+
13
+ def name_for(pattern)
14
+ example_time = coerce(Time.new, pattern)
15
+ case pattern.length
16
+ when 0
17
+ "every-minute"
18
+ when 1
19
+ example_time.strftime('Hourly at :%M')
20
+ when 2
21
+ example_time.strftime('Daily at %H:%M')
22
+ when 3
23
+ example_time.strftime('Monthly on day %d, at %H:%M')
24
+ when 4
25
+ example_time.strftime('Yearly: %b %d, at %H:%M')
26
+ else
27
+ raise ArgumentError, "argument out of range"
28
+ end
29
+
30
+ end
31
+
32
+ def freq_for(pattern)
33
+ case pattern.length
34
+ when 0
35
+ 1
36
+ when 1
37
+ 60
38
+ when 2
39
+ 60 * 24
40
+ when 3
41
+ 60 * 24 * 28
42
+ when 4
43
+ 60 * 24 * 365
44
+ else
45
+ raise ArgumentError, "argument out of range"
46
+ end
47
+ end
48
+
49
+ def adjustment_index
50
+ 5 - pattern.length
51
+ end
52
+
53
+ # [sec, min, hour, day, month, year, wday, yday, isdst, zone]
54
+ # new(year, month, day, hour, min, sec, utc_offset)
55
+ def coerce(time, pat=pattern)
56
+ existing = time.to_a
57
+ changeable = existing[((pat.length+1)..5)].reverse
58
+ values = changeable + pat + [0, time.utc_offset]
59
+ Time.new(*values) + 1
60
+ end
61
+
62
+ def compute_earliest_time(file_list)
63
+ exact_time = super
64
+ time = coerce(exact_time)
65
+ if time < exact_time
66
+ time
67
+ else
68
+ step_back(time)
69
+ end
70
+ end
71
+
72
+ def latest_time(file_list)
73
+ exact_time = super
74
+ time = coerce(exact_time)
75
+ if time > exact_time
76
+ time
77
+ else
78
+ step_forward(time)
79
+ end
80
+ end
81
+
82
+ def step_back(time)
83
+ coerce(super(time))
84
+ end
85
+
86
+ def step_forward(time)
87
+ coerce(super(time))
88
+ end
89
+ end
90
+ end
91
+ end
@@ -11,7 +11,13 @@ module BiblioTech
11
11
 
12
12
  def list
13
13
  files = []
14
- Dir.new(path).each do |file|
14
+ begin
15
+ dir = Dir.new(path)
16
+ rescue Errno::ENOENT
17
+ return []
18
+ end
19
+
20
+ dir.each do |file|
15
21
  next if %w{. ..}.include?(file)
16
22
  file_record = build_record(file)
17
23
  if file_record.nil?
@@ -35,6 +35,14 @@ module BiblioTech
35
35
  file_list.first.timestamp
36
36
  end
37
37
 
38
+ def step_back(time)
39
+ time - freq_seconds
40
+ end
41
+
42
+ def step_forward(time)
43
+ time + freq_seconds
44
+ end
45
+
38
46
  # Working from the latest time backwards, mark the closest file to the
39
47
  # appropriate frequencies as keepable
40
48
  def mark(original_file_list)
@@ -42,19 +50,24 @@ module BiblioTech
42
50
 
43
51
  time = latest_time(file_list)
44
52
  earliest_time = compute_earliest_time(file_list)
53
+ oldest_file = file_list.last
54
+
45
55
  while time > earliest_time do
46
56
  file_list.delete_if do |record|
47
57
  record.timestamp > time
48
58
  end
49
59
 
50
- break if file_list.empty?
60
+ if file_list.empty?
61
+ oldest_file.in_schedule(name)
62
+ break
63
+ end
51
64
 
52
65
  closest = file_list.first
53
66
 
54
67
  if (time - closest.timestamp) < freq_seconds
55
68
  closest.in_schedule(name)
56
69
  end
57
- time -= freq_seconds
70
+ time = step_back(time)
58
71
  end
59
72
  return original_file_list
60
73
  end
@@ -3,6 +3,10 @@ require 'bibliotech/application'
3
3
 
4
4
  module BiblioTech
5
5
  class CLI < Thor
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+
6
10
  desc "latest", "Outputs the latest DB dump available locally"
7
11
  def latest
8
12
  app = App.new(:log => { :target => "/dev/null" })
@@ -18,7 +22,7 @@ module BiblioTech
18
22
  desc "load FILENAME", "Load a database file from FILE"
19
23
  def load(file)
20
24
  app = App.new
21
- app.import(:backups => { :filename => file })
25
+ app.import(:backups => { :file => file })
22
26
  end
23
27
 
24
28
  desc "backup", "Create a database backup if needed"
@@ -26,8 +26,24 @@ module BiblioTech
26
26
  end
27
27
  end
28
28
 
29
- def import(options = nil)
30
- options = config.merge(options || {})
29
+ def import(opts = nil)
30
+ opts ||= {}
31
+ options = config.merge(opts)
32
+ unless File.exists?(options.backup_file)
33
+ filename = options.backup_file
34
+ opts[:backups] = opts.fetch(:backups){ opts.fetch("backups", {})}
35
+ opts[:filename] = filename
36
+
37
+ options = config.merge(opts)
38
+
39
+ if File.exists?(options.backup_file)
40
+ log.warn { "Actually restoring from #{options.backup_file} - this behavior is deprecated. In future, use explicit path."}
41
+ else
42
+ log.fatal{ "Cannot restore from database from missing file #{filename} !"}
43
+ raise "Missing #{filename}"
44
+ end
45
+ end
46
+
31
47
  command = cmd()
32
48
  command = Builders::Import.for(options).go(command)
33
49
  Builders::FileInput.for(options).go(command).tap do |cmd|
@@ -0,0 +1,130 @@
1
+ require 'bibliotech/backups/scheduler'
2
+ require 'bibliotech/backups/calendar_scheduler'
3
+
4
+ module BiblioTech
5
+ class Config
6
+ class Schedules
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+ attr_reader :config
11
+
12
+ SCHEDULE_SHORTHANDS = {
13
+ "hourly" => 60,
14
+ "hourlies" => 60,
15
+ "daily" => 60 * 24,
16
+ "dailies" => 60 * 24,
17
+ "weekly" => 60 * 24 * 7,
18
+ "weeklies" => 60 * 24 * 7,
19
+ "monthly" => 60 * 24 * 30,
20
+ "monthlies" => 60 * 24 * 30,
21
+ "quarterly" => 60 * 24 * 120,
22
+ "quarterlies" => 60 * 24 * 120,
23
+ "yearly" => 60 * 24 * 365,
24
+ "yearlies" => 60 * 24 * 365,
25
+ }.freeze
26
+ def regularize_frequency(frequency)
27
+ Integer( SCHEDULE_SHORTHANDS.fetch(frequency){ frequency } )
28
+ rescue ArgumentError
29
+ raise "#{frequency.inspect} is neither a number of minutes or a shorthand. Try:\n #{SCHEDULE_SHORTHANDS.keys.join(" ")}"
30
+ end
31
+
32
+ def backup_frequency
33
+ @backup_frequency ||= regularize_frequency(config.local_get(:backup_frequency))
34
+ end
35
+
36
+ def regularize_schedule(freq)
37
+ regularize_frequency(freq)
38
+ end
39
+
40
+ def schedules
41
+ config_hash.map do |frequency, limit|
42
+ next if limit == "none"
43
+ real_frequency = regularize_schedule(frequency)
44
+ limit =
45
+ case limit
46
+ when "all"
47
+ nil
48
+ else
49
+ Integer(limit)
50
+ end
51
+ [frequency, real_frequency, limit]
52
+ end.compact.map do |config_frequency, regularized, limit|
53
+ build_scheduler(config_frequency, regularized, limit).tap do |sched|
54
+ unless sched.frequency % backup_frequency == 0
55
+ raise "Pruning frequency #{sched.frequency}:#{config_frequency} is not a multiple " +
56
+ "of backup frequency: #{backup_frequency}:#{config.local_get(:backup_frequency)}"
57
+ end
58
+ end
59
+ end.compact.sort_by do |schedule|
60
+ schedule.frequency
61
+ end
62
+ end
63
+
64
+ def get_config_hash(name)
65
+ value = config.local_get(name)
66
+ if value.to_s == "none"
67
+ return {}
68
+ else
69
+ return value
70
+ end
71
+ end
72
+ end
73
+
74
+ class Periods < Schedules
75
+ def config_hash
76
+ hash = {}
77
+
78
+ config.optionally{ hash.merge! get_config_hash(:prune_schedule) }
79
+ config.optionally{ hash.merge! get_config_hash(:legacy_prune_schedule) }
80
+ hash
81
+ end
82
+
83
+ def build_scheduler(frequency, real_frequency, limit)
84
+ Backups::Scheduler.new(frequency, real_frequency, limit)
85
+ end
86
+ end
87
+
88
+ class Calendars < Schedules
89
+ def config_hash
90
+ hash = {}
91
+ config.optionally{ hash = get_config_hash(:prune_calendar) }
92
+ [:quarterly, :quarterlies, "quarterly", "quarterlies"].each do |qkey|
93
+ limit = hash.delete(qkey)
94
+ if limit
95
+ hash.merge!(
96
+ :first_quarter => limit,
97
+ :second_quarter => limit,
98
+ :third_quarter => limit,
99
+ :fourth_quarter => limit
100
+ )
101
+ end
102
+ end
103
+ hash
104
+ end
105
+
106
+ def regularize_schedule(freq)
107
+ case freq
108
+ when Array
109
+ freq
110
+ when "daily", "dailies", :daily, :dailies
111
+ [ 0, 00 ]
112
+ when "monthly", "monthlies", :monthly, :monthlies
113
+ [ 1, 0, 00 ] # on the 1st, at 0:00
114
+ when "first_quarter", "first-quarter", :first_quarter
115
+ [ 1, 1, 0, 00 ] # on the 1st of Jan, at 0:00
116
+ when "second_quarter", "second-quarter", :second_quarter
117
+ [ 4, 1, 0, 00 ] # on the 1st of Apr, at 0:00
118
+ when "third_quarter", "third-quarter", :third_quarter
119
+ [ 7, 1, 0, 00 ] # on the 1st of Jul, at 0:00
120
+ when "fourth_quarter", "fourth-quarter", :fourth_quarter
121
+ [ 10, 1, 0, 00 ] # on the 1st of Oct, at 0:00
122
+ end
123
+ end
124
+
125
+ def build_scheduler(frequency, schedule_pattern, limit)
126
+ Backups::CalendarScheduler.new(schedule_pattern, limit)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,4 +1,4 @@
1
- require 'bibliotech/backups/scheduler'
1
+ require 'bibliotech/config/schedule'
2
2
 
3
3
  module BiblioTech
4
4
  class Config
@@ -20,7 +20,9 @@ module BiblioTech
20
20
  :filename => [ "backups" , "filename" ] ,
21
21
  :backup_path => [ "backups" , "dir" ] ,
22
22
  :compressor => [ "backups" , "compress" ] ,
23
- :prune_schedule => [ "backups" , "keep" ] ,
23
+ :legacy_prune_schedule => [ "backups" , "keep" ] ,
24
+ :prune_schedule => [ "backups" , "retain" , "periodic" ] ,
25
+ :prune_calendar => [ "backups" , "retain" , "calendar" ] ,
24
26
  :backup_name => [ "backups" , "prefix" ] ,
25
27
  :backup_frequency => [ "backups" , "frequency" ] ,
26
28
  :db_adapter => [ "database_config" , "adapter" ] ,
@@ -204,60 +206,18 @@ module BiblioTech
204
206
  File::join(remote_path(remote), filename)
205
207
  end
206
208
 
207
- SCHEDULE_SHORTHANDS = {
208
- "hourly" => 60,
209
- "hourlies" => 60,
210
- "daily" => 60 * 24,
211
- "dailies" => 60 * 24,
212
- "weekly" => 60 * 24 * 7,
213
- "weeklies" => 60 * 24 * 7,
214
- "monthly" => 60 * 24 * 30,
215
- "monthlies" => 60 * 24 * 30,
216
- "quarterly" => 60 * 24 * 120,
217
- "quarterlies" => 60 * 24 * 120,
218
- "yearly" => 60 * 24 * 365,
219
- "yearlies" => 60 * 24 * 365,
220
- }
221
- def regularize_frequency(frequency)
222
- Integer( SCHEDULE_SHORTHANDS.fetch(frequency){ frequency } )
223
- rescue ArgumentError
224
- raise "#{frequency.inspect} is neither a number of minutes or a shorthand. Try:\n #{SCHEDULE_SHORTHANDS.keys.join(" ")}"
225
- end
226
-
227
209
  def backup_name
228
210
  local_get(:backup_name)
229
211
  end
230
212
 
231
- def backup_frequency
232
- @backup_frequency ||= regularize_frequency(local_get(:backup_frequency))
233
- end
234
213
 
235
214
  def prune_schedules
236
- prune_hash = local_get(:prune_schedule)
237
- prune_hash.map do |frequency, limit|
238
- next if limit == "none"
239
- real_frequency = regularize_frequency(frequency)
240
- unless real_frequency % backup_frequency == 0
241
- raise "Pruning frequency #{real_frequency}:#{frequency} is not a multiple of backup frequency: #{backup_frequency}:#{local_get(:backup_frequency)}"
242
- end
243
- limit =
244
- case limit
245
- when "all"
246
- nil
247
- else
248
- Integer(limit)
249
- end
250
- [frequency, real_frequency, limit]
251
- end.compact.sort_by do |freq_name, frequency, limit|
252
- frequency
253
- end.tap do |list|
215
+ list = Periods.new(self).schedules + Calendars.new(self).schedules
254
216
  if list.empty?
255
217
  require 'pp'
256
218
  raise "No backups will be kept by prune schedule: #{prune_hash.pretty_inspect}"
257
219
  end
258
- end.map do |freq_name, frequency, limit|
259
- Backups::Scheduler.new(freq_name, frequency, limit)
260
- end
220
+ list
261
221
  end
262
222
 
263
223
  def database_config
@@ -103,7 +103,6 @@ module BiblioTech
103
103
  end
104
104
 
105
105
  it "should keep 48 hours of backup" do
106
- now = Time.now.utc
107
106
  (0..47).each do |interval|
108
107
  sandbox.new :file => "db_backups/#{pruner.filename_for(Time.now.utc - interval * 60 * 60)}"
109
108
  end
@@ -122,7 +121,7 @@ module BiblioTech
122
121
  sandbox.new :file => "db_backups/#{pruner.filename_for(now - interval * 60 * 60)}"
123
122
  end
124
123
 
125
- expect(pruner.pruneable.length).to eq(471 - 48 - (14 - 2) - 1) # 2 dailies hourly etc.
124
+ expect(pruner.pruneable.length).to eq(471 - 48 - (14 - 2) - 2) # 2 dailies hourly etc.
126
125
 
127
126
  expect(pruner.list.length).to eq(471)
128
127
  end
@@ -26,13 +26,13 @@ module BiblioTech::Backups
26
26
  Scheduler.new("hourly", 60, nil)
27
27
  end
28
28
 
29
- context "when there's more than enough backups" do
29
+ context "when there's superfrequent backups over 12 hours" do
30
30
  let(:interval){ 60*60*12 - test_jitter}
31
31
  let(:frequency) { 15 }
32
32
  let(:test_jitter){ 60 }
33
33
 
34
- it "should mark 8 files kept" do
35
- expect(kept_files.count).to eql 12
34
+ it "should mark 13 files kept" do
35
+ expect(kept_files.count).to eql 13
36
36
  end
37
37
  end
38
38
  end
@@ -84,13 +84,13 @@ module BiblioTech::Backups
84
84
  end
85
85
  end
86
86
 
87
- context "when there are too few backups" do
87
+ context "when the total backup interval is too short" do
88
88
  let(:interval){ 60*60*4 - test_jitter }
89
89
  let(:frequency){ 60*8 }
90
90
  let(:test_jitter){ 60 }
91
91
 
92
- it "should mark 4 files kept" do
93
- expect(kept_files.count).to eql 4
92
+ it "should mark 5 files kept" do
93
+ expect(kept_files.count).to eql 5
94
94
  end
95
95
  end
96
96
 
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'pry'
3
+ require 'file-sandbox'
3
4
 
4
5
  module BiblioTech
5
6
  describe CommandGenerator, "for mysql" do
@@ -16,7 +17,7 @@ module BiblioTech
16
17
  let( :password ) { "password123" }
17
18
  let( :host ) { "127.0.0.1" }
18
19
  let( :filename ) { "export.sql" }
19
- let( :path ) { "/some/path" }
20
+ let( :path ) { "some/path" }
20
21
 
21
22
  let :base_config_hash do
22
23
  { "database_config" => {
@@ -119,6 +120,8 @@ module BiblioTech
119
120
 
120
121
 
121
122
  describe :import do
123
+ include FileSandbox
124
+
122
125
  let :command do
123
126
  generator.import(options)
124
127
  end
@@ -127,38 +130,50 @@ module BiblioTech
127
130
  command
128
131
  end
129
132
 
133
+
130
134
  context 'with username, database, file, and path' do
131
135
  let :options do
132
136
  { :backups => { :filename => filename, :dir => path }}
133
137
  end
134
138
 
135
- it { expect(command).to be_a(Caliph::CommandLine) }
136
-
137
- it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
138
- it { expect(command.executable).to eq('mysql')}
139
- it { expect(command.options).to eq(["-u #{username}", db_name ]) }
139
+ it "should raise an exception if load file is missing" do
140
+ expect{ command }.to raise_error(/Missing/i)
141
+ end
140
142
 
141
- context "plus password" do
142
- let :config_hash do
143
- base_config_hash.tap do |hash|
144
- hash["database_config"]["password"] = password
145
- end
143
+ context 'with existent backup file' do
144
+ before :each do
145
+ sandbox.new :file => File.join(path, filename)
146
+ sandbox.new :file => File.join(path, filename + '.gz')
146
147
  end
147
148
 
148
- it { expect(command.options).to eq(["-u #{username}","--password='#{password}'", "#{db_name}"]) }
149
+ it { expect(command).to be_a(Caliph::CommandLine) }
149
150
 
150
- context 'and compressor' do
151
- let :options do
152
- { :backups => {
153
- :filename => filename + '.gz',
154
- :dir => path,
155
- :compress => :gzip
156
- } }
151
+ it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
152
+ it { expect(command.executable).to eq('mysql')}
153
+ it { expect(command.options).to eq(["-u #{username}", db_name ]) }
154
+
155
+ context "plus password" do
156
+ let :config_hash do
157
+ base_config_hash.tap do |hash|
158
+ hash["database_config"]["password"] = password
159
+ end
157
160
  end
158
161
 
159
- it { expect(command).to be_a(Caliph::PipelineChain) }
160
- it { expect(first_cmd.executable).to eq('gunzip') }
161
- it { expect(first_cmd.options).to eq(["-c", "#{path}/#{filename}.gz"]) }
162
+ it { expect(command.options).to eq(["-u #{username}","--password='#{password}'", "#{db_name}"]) }
163
+
164
+ context 'and compressor' do
165
+ let :options do
166
+ { :backups => {
167
+ :filename => filename + '.gz',
168
+ :dir => path,
169
+ :compress => :gzip
170
+ } }
171
+ end
172
+
173
+ it { expect(command).to be_a(Caliph::PipelineChain) }
174
+ it { expect(first_cmd.executable).to eq('gunzip') }
175
+ it { expect(first_cmd.options).to eq(["-c", "#{path}/#{filename}.gz"]) }
176
+ end
162
177
  end
163
178
  end
164
179
  end
@@ -1,6 +1,9 @@
1
1
  require 'spec_helper'
2
+ require 'file-sandbox'
2
3
 
3
4
  module BiblioTech
5
+ include FileSandbox
6
+
4
7
  describe CommandGenerator do
5
8
  let :generator do
6
9
  CommandGenerator.new(config)
@@ -11,7 +14,7 @@ module BiblioTech
11
14
  let (:password){ "password123" }
12
15
  let (:host){ "127.0.0.1" }
13
16
  let (:filename){ "export.pg" }
14
- let (:path){ "/some/path" }
17
+ let (:path){ "some/path" }
15
18
 
16
19
 
17
20
  let (:base_options){{}}
@@ -135,6 +138,8 @@ module BiblioTech
135
138
 
136
139
 
137
140
  describe :import do
141
+ include FileSandbox
142
+
138
143
  let :command do
139
144
  generator.import(options)
140
145
  end
@@ -148,32 +153,43 @@ module BiblioTech
148
153
  base_options.merge(:backups => { :filename => filename, :dir => path })
149
154
  end
150
155
 
151
- it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
152
- it { expect(command.executable).to eq('pg_restore')}
153
- it { expect(command.options).to eq(["-Oc", "-U #{username}", "-d #{db_name}" ]) }
156
+ it "should raise an exception if load file is missing" do
157
+ expect{ command }.to raise_error(/Missing/i)
158
+ end
154
159
 
155
- context "plus password" do
156
- let :config_hash do
157
- base_config_hash.tap do |hash|
158
- hash["database_config"]["password"] = password
159
- end
160
+ context "where file exists" do
161
+ before :each do
162
+ sandbox.new :file => File.join(path, filename)
163
+ sandbox.new :file => File.join(path, filename + '.gz')
160
164
  end
161
165
 
162
- it { expect(command.options).to eq(["-Oc", "-U #{username}", "-d #{db_name}"]) }
163
- it { expect(command.env['PGPASSWORD']).to eq(password) }
166
+ it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
167
+ it { expect(command.executable).to eq('pg_restore')}
168
+ it { expect(command.options).to eq(["-Oc", "-U #{username}", "-d #{db_name}" ]) }
164
169
 
165
- context 'and compressor' do
166
- let :options do
167
- base_options.merge(:backups => {
168
- :filename => filename + '.gz',
169
- :dir => path,
170
- :compressor => :gzip
171
- })
170
+ context "plus password" do
171
+ let :config_hash do
172
+ base_config_hash.tap do |hash|
173
+ hash["database_config"]["password"] = password
174
+ end
172
175
  end
173
176
 
174
- it { expect(command).to be_a(Caliph::PipelineChain) }
175
- it { expect(first_cmd.executable).to eq('gunzip') }
176
- it { expect(first_cmd.options).to eq(["-c", "#{path}/#{filename}.gz"]) }
177
+ it { expect(command.options).to eq(["-Oc", "-U #{username}", "-d #{db_name}"]) }
178
+ it { expect(command.env['PGPASSWORD']).to eq(password) }
179
+
180
+ context 'and compressor' do
181
+ let :options do
182
+ base_options.merge(:backups => {
183
+ :filename => filename + '.gz',
184
+ :dir => path,
185
+ :compressor => :gzip
186
+ })
187
+ end
188
+
189
+ it { expect(command).to be_a(Caliph::PipelineChain) }
190
+ it { expect(first_cmd.executable).to eq('gunzip') }
191
+ it { expect(first_cmd.options).to eq(["-c", "#{path}/#{filename}.gz"]) }
192
+ end
177
193
  end
178
194
  end
179
195
  end
@@ -101,7 +101,7 @@ module BiblioTech
101
101
  end
102
102
  end
103
103
 
104
- describe "schedule shorthands" do
104
+ describe "schedule configuration" do
105
105
  let :config do
106
106
  Config.new(nil).tap do |config|
107
107
  config.hash = config_hash
@@ -111,21 +111,108 @@ module BiblioTech
111
111
  let :config_hash do
112
112
  { "backups" => {
113
113
  "frequency" => 60,
114
- "keep" => {
115
- 60 => 24,
116
- 1440 => 7
117
- }}}
114
+ "retain" => {
115
+ "periodic" => {
116
+ 60 => 24,
117
+ 1440 => 7
118
+ },
119
+ "calendar" => {
120
+ [ 1,1,1 ] => 10
121
+ }
122
+ }
123
+ }}
124
+ end
125
+
126
+ let :all_schedules do
127
+ config.prune_schedules
118
128
  end
119
129
 
120
- let :schedule_array do
121
- config.prune_schedules.map do |sched|
130
+ let :periodic_array do
131
+ all_schedules.find_all{|sch| sch.class == Backups::Scheduler}.map do |sched|
122
132
  [sched.frequency, sched.limit]
123
133
  end
124
134
  end
125
135
 
136
+ let :calendar_array do
137
+ all_schedules.find_all{|sch| sch.is_a? Backups::CalendarScheduler}.map do |sched|
138
+ [sched.pattern, sched.limit]
139
+ end
140
+ end
141
+
126
142
  context "simple numerics" do
127
143
  it "should produce correct schedule" do
128
- expect(schedule_array).to contain_exactly([60, 24], [1440, 7])
144
+ expect(periodic_array).to contain_exactly([60, 24], [1440, 7])
145
+ expect(calendar_array).to contain_exactly([[1,1,1],10])
146
+ end
147
+ end
148
+
149
+ context "legacy overrides" do
150
+ let :config_hash do
151
+ { "backups" => {
152
+ "frequency" => 60,
153
+ "keep" => {
154
+ 60 => 12,
155
+ 2400 => 6
156
+ },
157
+ "calendar" => {
158
+ [ 1,1,1 ] => 8,
159
+ [ 2,1,1 ] => 8
160
+ },
161
+ "retain" => {
162
+ "periodic" => {
163
+ 60 => 24,
164
+ 1440 => 7
165
+ },
166
+ "calendar" => {
167
+ [ 1,1,1 ] => 10
168
+ }
169
+ }
170
+ }}
171
+ end
172
+
173
+ # because it exists in deployment, and wants to be fixed
174
+ it "should prefer the legacy config" do
175
+ expect(periodic_array).to contain_exactly([60, 12], [1440, 7], [2400, 6])
176
+ expect(calendar_array).to contain_exactly([[1,1,1],10])
177
+ end
178
+ end
179
+
180
+ context "complete removal of calendar scedule" do
181
+ let :config_hash do
182
+ { "backups" => {
183
+ "frequency" => 60,
184
+ "retain" => {
185
+ "periodic" => {
186
+ 60 => 24,
187
+ 1440 => 7
188
+ },
189
+ "calendar" => "none"
190
+ }
191
+ }}
192
+ end
193
+
194
+ it "should prefer the modern config" do
195
+ expect(periodic_array).to contain_exactly([60, 24], [1440, 7])
196
+ expect(calendar_array).to be_empty
197
+ end
198
+ end
199
+
200
+ context "complete removal of periodic scedule" do
201
+ let :config_hash do
202
+ { "backups" => {
203
+ "frequency" => 60,
204
+ "retain" => {
205
+ "periodic" => "none",
206
+ "calendar" => {
207
+ [ 1,1,1 ] => 10
208
+ }
209
+ }
210
+ }}
211
+ end
212
+
213
+ it "should prefer the modern config" do
214
+ expect(periodic_array).to be_empty
215
+ expect(calendar_array).to contain_exactly([[1,1,1],10])
129
216
  end
130
217
  end
131
218
 
@@ -148,7 +235,7 @@ module BiblioTech
148
235
  end
149
236
 
150
237
  it "should prefer the local config" do
151
- expect(schedule_array).to contain_exactly([60, 12])
238
+ expect(periodic_array).to contain_exactly([60, 12])
152
239
  end
153
240
  end
154
241
 
@@ -164,7 +251,7 @@ module BiblioTech
164
251
 
165
252
  it "should raise an error" do
166
253
  expect do
167
- schedule_array
254
+ periodic_array
168
255
  end.to raise_error(/59/)
169
256
  end
170
257
  end
@@ -182,7 +269,7 @@ module BiblioTech
182
269
 
183
270
  it "should raise an error" do
184
271
  expect do
185
- schedule_array
272
+ periodic_array
186
273
  end.to raise_error
187
274
  end
188
275
  end
@@ -200,7 +287,7 @@ module BiblioTech
200
287
  end
201
288
 
202
289
  it "should not raise error" do
203
- expect(schedule_array).to contain_exactly([60*24, nil])
290
+ expect(periodic_array).to contain_exactly([60*24, nil])
204
291
  end
205
292
  end
206
293
 
@@ -217,7 +304,7 @@ module BiblioTech
217
304
 
218
305
  it "should raise an error" do
219
306
  expect do
220
- schedule_array
307
+ periodic_array
221
308
  end.to raise_error
222
309
  end
223
310
  end
@@ -226,16 +313,26 @@ module BiblioTech
226
313
  let :config_hash do
227
314
  { "backups" => {
228
315
  "frequency" => "hourly",
229
- "keep" => {
230
- "hourlies" => 24,
231
- "daily" => 7,
232
- "weeklies" => 4,
233
- "monthly" => "all"
234
- }}}
316
+ "retain" => {
317
+ "periodic" => {
318
+ "hourlies" => 24,
319
+ "daily" => 7,
320
+ "weeklies" => 4,
321
+ "monthly" => "all"
322
+ },
323
+ "calendar" => {
324
+ "monthly" => 12,
325
+ "quarterly" => "all"
326
+ }
327
+ }
328
+ }}
235
329
  end
236
330
 
237
331
  it "should produce correct schedule" do
238
- expect(schedule_array).to contain_exactly([60, 24], [60*24, 7], [60*24*7, 4], [60*24*30, nil])
332
+ expect(periodic_array).to contain_exactly([60, 24], [60*24, 7], [60*24*7, 4], [60*24*30, nil])
333
+ expect(calendar_array).to contain_exactly(
334
+ [[1,0,0],12], [[1,1,0,0],nil],[[4,1,0,0],nil],[[7,1,0,0],nil], [[10,1,0,0],nil]
335
+ )
239
336
  end
240
337
  end
241
338
  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.7.1
4
+ version: 0.8.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: 2015-06-02 00:00:00.000000000 Z
12
+ date: 2015-12-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caliph
@@ -79,47 +79,49 @@ extra_rdoc_files:
79
79
  - doc/todo.txt
80
80
  - doc/example_config_file.yml
81
81
  files:
82
+ - bin/bibliotech
82
83
  - default_configuration/config.yaml
84
+ - doc/example_config_file.yml
85
+ - doc/todo.txt
86
+ - lib/bibliotech.rb
87
+ - lib/bibliotech/application.rb
88
+ - lib/bibliotech/backups/calendar_scheduler.rb
89
+ - lib/bibliotech/backups/file_record.rb
90
+ - lib/bibliotech/backups/prune_list.rb
91
+ - lib/bibliotech/backups/pruner.rb
92
+ - lib/bibliotech/backups/scheduler.rb
83
93
  - lib/bibliotech/builders.rb
84
94
  - lib/bibliotech/builders/database.rb
85
- - lib/bibliotech/builders/gzip.rb
86
95
  - lib/bibliotech/builders/file.rb
87
- - lib/bibliotech/builders/postgres.rb
96
+ - lib/bibliotech/builders/gzip.rb
88
97
  - lib/bibliotech/builders/mysql.rb
89
- - lib/bibliotech/config.rb
90
- - lib/bibliotech/logger.rb
91
- - lib/bibliotech/command_runner.rb
98
+ - lib/bibliotech/builders/postgres.rb
99
+ - lib/bibliotech/cli.rb
92
100
  - lib/bibliotech/command_generator.rb
93
- - lib/bibliotech/rake_lib.rb
101
+ - lib/bibliotech/command_runner.rb
94
102
  - lib/bibliotech/compression.rb
95
103
  - lib/bibliotech/compression/bzip2.rb
96
104
  - lib/bibliotech/compression/gzip.rb
97
105
  - lib/bibliotech/compression/sevenzip.rb
98
- - lib/bibliotech/backups/scheduler.rb
99
- - lib/bibliotech/backups/pruner.rb
100
- - lib/bibliotech/backups/prune_list.rb
101
- - lib/bibliotech/backups/file_record.rb
102
- - lib/bibliotech/application.rb
103
- - lib/bibliotech/cli.rb
106
+ - lib/bibliotech/config.rb
107
+ - lib/bibliotech/config/schedule.rb
108
+ - lib/bibliotech/logger.rb
104
109
  - lib/bibliotech/railtie.rb
105
- - lib/bibliotech.rb
106
- - bin/bibliotech
107
- - spec/spec_helper.rb
108
- - spec/bibliotech/config_spec.rb
109
- - spec/bibliotech/command_generator/postgres_spec.rb
110
- - spec/bibliotech/command_generator/mysql_spec.rb
110
+ - lib/bibliotech/rake_lib.rb
111
111
  - spec/bibliotech/backup_pruner_spec.rb
112
- - spec/bibliotech/compression_spec.rb
113
- - spec/bibliotech/compression/bzip2_spec.rb
114
- - spec/bibliotech/compression/sevenzip_spec.rb
115
- - spec/bibliotech/compression/bunzip2_spec.rb
116
- - spec/bibliotech/compression/gzip_spec.rb
117
112
  - spec/bibliotech/backup_scheduler_spec.rb
113
+ - spec/bibliotech/command_generator/mysql_spec.rb
114
+ - spec/bibliotech/command_generator/postgres_spec.rb
118
115
  - spec/bibliotech/command_generator_spec.rb
119
116
  - spec/bibliotech/command_runner_spec.rb
117
+ - spec/bibliotech/compression/bunzip2_spec.rb
118
+ - spec/bibliotech/compression/bzip2_spec.rb
119
+ - spec/bibliotech/compression/gzip_spec.rb
120
+ - spec/bibliotech/compression/sevenzip_spec.rb
121
+ - spec/bibliotech/compression_spec.rb
122
+ - spec/bibliotech/config_spec.rb
120
123
  - spec/gem_test_suite.rb
121
- - doc/todo.txt
122
- - doc/example_config_file.yml
124
+ - spec/spec_helper.rb
123
125
  homepage: ''
124
126
  licenses:
125
127
  - MIT
@@ -130,7 +132,7 @@ rdoc_options:
130
132
  - --main
131
133
  - doc/README
132
134
  - --title
133
- - bibliotech-0.7.1 Documentation
135
+ - bibliotech-0.8.0 Documentation
134
136
  require_paths:
135
137
  - lib/
136
138
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -145,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
147
  version: '0'
146
148
  requirements: []
147
149
  rubyforge_project: bibliotech
148
- rubygems_version: 2.0.14
150
+ rubygems_version: 2.4.8
149
151
  signing_key:
150
152
  specification_version: 4
151
153
  summary: ''