martilla 0.2.9 → 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
  SHA256:
3
- metadata.gz: 3e7aa68d0f99f6f614717b71f8952b505471eb1cff84a99808eb319de33daf57
4
- data.tar.gz: eda97326ca0b55f78e2240ab7596eca8b8721700854930080dda9b544254cc3e
3
+ metadata.gz: aaf89471f6777ba461ddf9769030ef39b30a019c76e10dc35236cf8bcca03a53
4
+ data.tar.gz: 192c63a35a2e7d0381c45643d86defe699bcdaadc750ccbf8c782aeadf52fbbb
5
5
  SHA512:
6
- metadata.gz: 45943b19f5fa5d9827dab8279fbc2dd610eb04f7e146674618ff941f94fd8ee31696243cdccaa9f8e7f7310be55c4ee135264457973c3c85c45e447fa659bb32
7
- data.tar.gz: b78c7f5916337c4f8e382547a3721cd08f0b85944198ea4dd195f00a78a21cd4dc74dc343ba7d7f42a32232cbe94d47bb619dffb0fd44716f4c4a51cccde5e67
6
+ metadata.gz: 596ccdd75c799206a00f363ccd9c203eb93671c70296127efb197087d621e2209fa584f4ef73b1f5fe2fa9ff5d895807e926bf32764e752803eb02eadff320b3
7
+ data.tar.gz: dbada619ce14429929942cd842396d5aae9551bcc177e2438b48dabdc79d5623adb63d73db752e23fef0f575579412f5eac4014c0e20a26f153f02b4d8f66efc
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /backups/
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
@@ -14,3 +15,6 @@
14
15
  martilla.yml
15
16
  backup.sql
16
17
  backup.sql.gz
18
+
19
+ test-database-dump.sql
20
+ test-database-dump.sql.gz
data/.travis.yml CHANGED
@@ -3,5 +3,8 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.6.3
6
+ - 2.6.5
7
+ - 2.5.7
8
+ - 2.4.9
7
9
  before_install: gem install bundler -v 2.0.2
10
+ script: bundle exec rspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- martilla (0.2.9)
4
+ martilla (0.3.0)
5
5
  aws-sdk-s3 (~> 1.49)
6
6
  aws-sdk-ses (~> 1.26)
7
7
  memoist (~> 0.16.0)
@@ -13,8 +13,8 @@ GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
15
  aws-eventstream (1.0.3)
16
- aws-partitions (1.231.0)
17
- aws-sdk-core (3.72.0)
16
+ aws-partitions (1.237.0)
17
+ aws-sdk-core (3.76.0)
18
18
  aws-eventstream (~> 1.0, >= 1.0.2)
19
19
  aws-partitions (~> 1, >= 1.228.0)
20
20
  aws-sigv4 (~> 1.1)
@@ -22,7 +22,7 @@ GEM
22
22
  aws-sdk-kms (1.25.0)
23
23
  aws-sdk-core (~> 3, >= 3.71.0)
24
24
  aws-sigv4 (~> 1.1)
25
- aws-sdk-s3 (1.52.0)
25
+ aws-sdk-s3 (1.53.0)
26
26
  aws-sdk-core (~> 3, >= 3.71.0)
27
27
  aws-sdk-kms (~> 1)
28
28
  aws-sigv4 (~> 1.1)
@@ -39,7 +39,7 @@ GEM
39
39
  json (2.2.0)
40
40
  mail (2.7.1)
41
41
  mini_mime (>= 0.1.1)
42
- memoist (0.16.0)
42
+ memoist (0.16.1)
43
43
  method_source (0.9.2)
44
44
  mini_mime (1.0.2)
45
45
  pony (1.13.1)
data/README.md CHANGED
@@ -72,10 +72,12 @@ Currently available DB types to choose from are **postgres** & **mysql**. They b
72
72
 
73
73
  ### Storages
74
74
 
75
- The available Storages types are 'local', 'S3' & 'SCP'. They each have different available options:
75
+ The available Storages types are **local**, **S3**& **SCP**. They each have different available options:
76
76
  - options for type: **local**
77
77
  - `filename`
78
78
  - The location to where the backup will be stored
79
+ - `retention`
80
+ - An integer that defines the max number of backups stored at the defined location
79
81
  - options for type: **s3**
80
82
  - `filename`
81
83
  - The location to where the backup will be stored within the S3 bucket
@@ -85,51 +87,61 @@ The available Storages types are 'local', 'S3' & 'SCP'. They each have different
85
87
  - can be specified with the usual ENV variables or IAM roles
86
88
  - `secret_access_key`
87
89
  - can be specified with the usual ENV variables or IAM roles
90
+ - `retention`
91
+ - An integer that defines the max number of backups stored at the defined location
88
92
  - options for type: **scp**
89
93
  - `filename`
90
94
  - The location to where the backup will be stored within remote server
91
95
  - `host`
92
96
  - `user`
93
97
  - `identity_file`
98
+ - `retention`
99
+ - Not implemented for this storage ([see #12](https://github.com/fdoxyz/martilla/issues/12))
94
100
 
95
101
  All storage types also accept 'suffix' as a boolean that enables or disables a timestamp to be added as a suffix to the backup 'filename', it defaults as `true`.
96
102
 
97
103
  ### Notifiers
98
104
 
99
- The available Notifiers are 'ses', 'sendmail' & 'smtp'. They each have different available options:
100
- - options for type: **ses** (email notifier)
101
- - `aws_region`
102
- - `access_key_id`
103
- - can be specified with the usual ENV variables or IAM role
104
- - `secret_access_key`
105
- - can be specified with the usual ENV variables or IAM roles
106
- - options for type: **sendmail** (email notifier)
107
- - no custom options
108
- - options for type: **smtp** (email notifier)
109
- - `address`
110
- - `domain`
111
- - `user_name`
112
- - `password`
113
- - options for type: **slack**
114
- - `slack_webhook_url`
115
- - required
116
- - more info [here](https://api.slack.com/messaging/webhooks)
117
- - `slack_channel`
118
- - defaults to `#general`
119
- - the channel to post the backup notifications
120
- - `slack_username`
121
- - defaults to `Martilla`
122
- - the username which backup notifications will be posted with
105
+ The available Notifiers are **ses**, **sendmail** & **smtp**. They each have different available options:
106
+ - options for type: **ses** (email notifier)
107
+ - `aws_region`
108
+ - `access_key_id`
109
+ - can be specified with the usual ENV variables or IAM role
110
+ - `secret_access_key`
111
+ - can be specified with the usual ENV variables or IAM roles
112
+ - options for type: **sendmail** (email notifier)
113
+ - no custom options
114
+ - options for type: **smtp** (email notifier)
115
+ - `address`
116
+ - `domain`
117
+ - `user_name`
118
+ - `password`
119
+ - options for type: **slack**
120
+ - `slack_webhook_url`
121
+ - required
122
+ - more info [here](https://api.slack.com/messaging/webhooks)
123
+ - `slack_channel`
124
+ - defaults to `#general`
125
+ - the channel to post the backup notifications
126
+ - `slack_username`
127
+ - defaults to `Martilla`
128
+ - the username which backup notifications will be posted with
123
129
 
124
130
  All of the previous **email notifiers** also have the following options that can be customized:
125
- - `to`
126
- - a list of comma separated emails to be notified
127
- - `from`
128
- - defaults to 'martilla@no-reply.com'
129
- - `success_subject`
130
- - the subject of the success email
131
- - `failure_subject`
132
- - the subject of the failure email
131
+ - `to`
132
+ - a list of comma separated emails to be notified
133
+ - `from`
134
+ - defaults to 'martilla@no-reply.com'
135
+ - `success_subject`
136
+ - the subject of the success email
137
+ - `failure_subject`
138
+ - the subject of the failure email
139
+
140
+ Also **ALL** notifiers have the following two options
141
+ - `send_success`
142
+ - `Boolean` value that will disable notifications on success when set to false. Defaults to `true`
143
+ - `send_failure`
144
+ - `Boolean` value that will disable notifications on failure when set to false. Defaults to `true`
133
145
 
134
146
  It's **HIGHLY RECOMMENDED** to test and make sure emails are being delivered correctly to each target inbox. Emails with standard messages like these automated backup notifications tend to be easily marked as spam.
135
147
 
@@ -45,14 +45,15 @@ module Martilla
45
45
 
46
46
  # Persist the backup
47
47
  @storage.persist(tmp_file: tmp_file, gzip: gzip?)
48
+ @storage.enfore_retention!(gzip: gzip?)
48
49
  rescue Exception => e
49
50
  @notifiers.each do |notifier|
50
- notifier.error(e.message, metadata)
51
+ notifier.error(e.message, metadata) if notifier.send_failure?
51
52
  end
52
53
  puts "An error occurred: #{e.inspect}"
53
54
  else
54
55
  @notifiers.each do |notifier|
55
- notifier.success(metadata)
56
+ notifier.success(metadata) if notifier.send_success?
56
57
  end
57
58
  puts "Backup created and persisted successfully"
58
59
  end
@@ -101,7 +102,8 @@ module Martilla
101
102
  'storage' => {
102
103
  'type' => 'local',
103
104
  'options' => {
104
- 'filename' => 'database-backup.sql'
105
+ 'filename' => 'database-backup.sql',
106
+ 'retention' => 0
105
107
  }
106
108
  },
107
109
  'notifiers' => [
@@ -14,7 +14,7 @@ module Martilla
14
14
  private
15
15
 
16
16
  def connection_arguments
17
- "-u #{user} --password=#{password} -P #{port} #{db}"
17
+ "-u #{user} --password=#{password} --host=#{host} -P #{port} #{db}"
18
18
  end
19
19
 
20
20
  def host
@@ -1,5 +1,3 @@
1
- require 'forwardable'
2
-
3
1
  module Martilla
4
2
  class Notifier < Component
5
3
  attr_reader :options
@@ -21,6 +19,18 @@ module Martilla
21
19
  'Notifier configuration is invalid. Details here: https://github.com/fdoxyz/martilla'
22
20
  end
23
21
 
22
+ def send_success?
23
+ value = @options['send_success']
24
+ return true if value.nil?
25
+ value
26
+ end
27
+
28
+ def send_failure?
29
+ value = @options['send_failure']
30
+ return true if value.nil?
31
+ value
32
+ end
33
+
24
34
  # When a new Notifier is supported it needs to go here
25
35
  def self.create(config = {})
26
36
  case config['type'].downcase
@@ -35,9 +35,7 @@ module Martilla
35
35
  begin
36
36
  ses_client.send_email(
37
37
  destination: {
38
- to_addresses: [
39
- to_email.split(',')
40
- ]
38
+ to_addresses: to_email.split(',')
41
39
  },
42
40
  message: {
43
41
  body: {
@@ -25,8 +25,9 @@ module Martilla
25
25
  end
26
26
 
27
27
  def slack_webhook_url
28
- raise config_error('slack_webhook_url') if @options['slack_webhook_url'].nil?
29
- @options['slack_webhook_url']
28
+ webhook_url = @options['slack_webhook_url'] || ENV['SLACK_WEBHOOK_URL']
29
+ raise config_error('slack_webhook_url') if webhook_url.nil?
30
+ webhook_url
30
31
  end
31
32
 
32
33
  def slack_channel
@@ -13,6 +13,10 @@ module Martilla
13
13
  raise NotImplementedError, 'You must implement the persist method'
14
14
  end
15
15
 
16
+ def enforce_retention!
17
+ raise NotImplementedError, 'You must implement the enforce_retention! method'
18
+ end
19
+
16
20
  def invalid_options_msg
17
21
  'Storage configuration is invalid. Details here: https://github.com/fdoxyz/martilla'
18
22
  end
@@ -42,6 +46,14 @@ module Martilla
42
46
  "#{dirname}/#{basename}_#{timestamp}#{extension}"
43
47
  end
44
48
 
49
+ def retention_limit
50
+ @options['retention'].to_i
51
+ end
52
+
53
+ def timestamp_regex
54
+ /\d{4}-\d{2}-\d{2}T\d{6}/
55
+ end
56
+
45
57
  def config_error(config_name)
46
58
  Error.new("Storage adapter configuration requires #{config_name}. Details here: https://github.com/fdoxyz/martilla")
47
59
  end
@@ -5,5 +5,34 @@ module Martilla
5
5
  return nil if $?.success?
6
6
  raise Error.new("Local storage failed with code #{$?.exitstatus}")
7
7
  end
8
+
9
+ def enfore_retention!(gzip:)
10
+ return if retention_limit < 1
11
+ files = backup_file_list(output_filename(gzip))
12
+
13
+ while files.count > retention_limit do
14
+ File.delete(files.first)
15
+ puts "Retention limit met. Removed the backup file: #{files.shift}"
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # Oldest first & most recent last
22
+ def backup_file_list(sample_filename)
23
+ dirname = File.dirname(sample_filename)
24
+ basename = File.basename(sample_filename, '.*')
25
+
26
+ if suffix?
27
+ # Replaces file's basename timestamp for wildcards to match againts
28
+ # what exist in the directory. Ex: "backup_2019-11-10T114342" will be
29
+ # replaced with "backup_*-*-*T*". This means all backups will match
30
+ # using `Dir.glob` below
31
+ sections = basename.split('_').reject { |str| timestamp_regex =~ str }
32
+ basename = "#{sections.join('_')}_*-*-*T*"
33
+ end
34
+
35
+ Dir["#{dirname}/#{basename}.*"].sort_by { |f| File.mtime(f) }
36
+ end
8
37
  end
9
38
  end
@@ -11,6 +11,16 @@ module Martilla
11
11
  raise Error.new('Error uploading backup to bucket')
12
12
  end
13
13
 
14
+ def enfore_retention!(gzip:)
15
+ return if retention_limit < 1
16
+
17
+ objs = s3_resource.bucket(bucket_name).objects.sort_by(&:last_modified)
18
+ while objs.count > retention_limit do
19
+ objs.first.delete
20
+ puts "Retention limit met. Removed the backup file: #{objs.shift.key}"
21
+ end
22
+ end
23
+
14
24
  private
15
25
 
16
26
  def s3_resource
@@ -6,6 +6,10 @@ module Martilla
6
6
  raise Error.new("SCP storage failed with code #{$?.exitstatus}")
7
7
  end
8
8
 
9
+ def enfore_retention!(gzip:)
10
+ puts 'WARNING: Retention is not implemented for SCP storage. More details: https://github.com/fdoxyz/martilla/issues/12'
11
+ end
12
+
9
13
  private
10
14
 
11
15
  def host
@@ -1,3 +1,3 @@
1
1
  module Martilla
2
- VERSION = '0.2.9'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: martilla
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fernando Valverde
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-31 00:00:00.000000000 Z
11
+ date: 2019-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor