martilla 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ require 'pony'
2
+
3
+ module Martilla
4
+ class Sendmail < EmailNotifier
5
+ def success(data)
6
+ Pony.mail(to: to_email,
7
+ from: from_email,
8
+ subject: success_subject,
9
+ html_body: success_html(data),
10
+ body: success_txt(data))
11
+ end
12
+
13
+ def error(msg, data)
14
+ Pony.mail(to: to_email,
15
+ from: from_email,
16
+ subject: failure_subject,
17
+ html_body: error_html(msg, data),
18
+ body: error_txt(msg, data))
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,89 @@
1
- class Martilla::Notifiers::Ses < Martilla::Notifiers::Base
2
- def notify(opts)
1
+ require 'aws-sdk-ses'
3
2
 
3
+ module Martilla
4
+ class Ses < EmailNotifier
5
+ def success(data)
6
+ begin
7
+ ses_client.send_email(
8
+ destination: {
9
+ to_addresses: to_email.split(',')
10
+ },
11
+ message: {
12
+ body: {
13
+ html: {
14
+ charset: 'UTF-8',
15
+ data: success_html(data)
16
+ },
17
+ text: {
18
+ charset: 'UTF-8',
19
+ data: success_txt(data)
20
+ }
21
+ },
22
+ subject: {
23
+ charset: 'UTF-8',
24
+ data: success_subject
25
+ }
26
+ },
27
+ source: from_email
28
+ )
29
+ rescue Aws::SES::Errors::ServiceError => error
30
+ puts "Email not sent. Error message: #{error}"
31
+ end
32
+ end
33
+
34
+ def error(msg, data)
35
+ begin
36
+ ses_client.send_email(
37
+ destination: {
38
+ to_addresses: [
39
+ to_email.split(',')
40
+ ]
41
+ },
42
+ message: {
43
+ body: {
44
+ html: {
45
+ charset: 'UTF-8',
46
+ data: error_html(msg, data)
47
+ },
48
+ text: {
49
+ charset: 'UTF-8',
50
+ data: error_txt(msg, data)
51
+ }
52
+ },
53
+ subject: {
54
+ charset: 'UTF-8',
55
+ data: failure_subject
56
+ }
57
+ },
58
+ source: from_email
59
+ )
60
+ rescue Aws::SES::Errors::ServiceError => error
61
+ puts "Email not sent. Error message: #{error}"
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def ses_client
68
+ options = {}
69
+ options[:region] = aws_region unless aws_region.nil?
70
+ options[:access_key_id] = aws_access_key unless aws_access_key.nil?
71
+ options[:secret_access_key] = aws_secret_key unless aws_secret_key.nil?
72
+ Aws::SES::Client.new(options)
73
+ end
74
+
75
+ def aws_region
76
+ aws_region = @options['region']
77
+ raise config_error('region') if aws_region.nil?
78
+ aws_region
79
+ end
80
+
81
+ def aws_access_key
82
+ @options['access_key_id']
83
+ end
84
+
85
+ def aws_secret_key
86
+ @options['secret_access_key']
87
+ end
4
88
  end
5
89
  end
@@ -1,5 +1,4 @@
1
- class Martilla::Notifiers::Slack < Martilla::Notifiers::Base
2
- def notify(opts)
3
-
1
+ module Martilla
2
+ class Slack < Notifier
4
3
  end
5
4
  end
@@ -0,0 +1,70 @@
1
+ require 'pony'
2
+
3
+ module Martilla
4
+ class Smtp < EmailNotifier
5
+ def success(data)
6
+ Pony.mail(to: to_email,
7
+ from: from_email,
8
+ subject: success_subject,
9
+ via: :smtp,
10
+ html_body: success_html(data),
11
+ body: success_txt(data),
12
+ via_options: via_options)
13
+ end
14
+
15
+ def error(msg, data)
16
+ Pony.mail(to: to_email,
17
+ from: from_email,
18
+ subject: failure_subject,
19
+ via: :smtp,
20
+ html_body: error_html(msg, data),
21
+ body: error_txt(msg, data),
22
+ via_options: via_options)
23
+ end
24
+
25
+ private
26
+
27
+ def via_options
28
+ {
29
+ address: address,
30
+ port: port,
31
+ user_name: user_name,
32
+ password: password,
33
+ authentication: authentication, # :plain, :login, :cram_md5, no auth by default
34
+ domain: domain # the HELO domain provided by the client to the server
35
+ }
36
+ end
37
+
38
+ def address
39
+ email = @options['address']
40
+ raise config_error('address') if email.nil?
41
+ email
42
+ end
43
+
44
+ def domain
45
+ smtp_domain = @options['domain']
46
+ raise config_error('domain') if smtp_domain.nil?
47
+ smtp_domain
48
+ end
49
+
50
+ def user_name
51
+ smtp_user_name = @options['user_name']
52
+ raise config_error('user_name') if smtp_user_name.nil?
53
+ smtp_user_name
54
+ end
55
+
56
+ def password
57
+ smtp_password = @options['password']
58
+ raise config_error('password') if smtp_password.nil?
59
+ smtp_password
60
+ end
61
+
62
+ def port
63
+ @options['port'] || '25'
64
+ end
65
+
66
+ def authentication
67
+ @options['authentication'] || :plain
68
+ end
69
+ end
70
+ end
@@ -2,22 +2,62 @@ require 'forwardable'
2
2
 
3
3
  module Martilla
4
4
  class Storage
5
- extend Forwardable
5
+ attr_reader :options
6
6
 
7
7
  def initialize(config)
8
- # When a new core target is added to the project include it here
8
+ @options = config
9
+ raise Error.new(invalid_options_msg) if @options.nil?
10
+ end
11
+
12
+ def persist(temp_file:, gzip:)
13
+ raise NotImplementedError, 'You must implement the persist method'
14
+ end
15
+
16
+ def invalid_options_msg
17
+ 'Storage configuration is invalid. Details here: https://github.com/fdoxyz/martilla'
18
+ end
19
+
20
+ def suffix?
21
+ return true if @options['suffix'].nil?
22
+ @options['suffix']
23
+ end
24
+
25
+ def output_filename(gzip)
26
+ filename = @options['filename'] || 'backup.sql'
27
+ filename = append_datetime_suffix(filename) if suffix?
28
+ filename = "#{filename}.gz" if gzip
29
+ filename
30
+ end
31
+
32
+ def append_datetime_suffix(filename)
33
+ dirname = File.dirname(filename)
34
+ basename = File.basename(filename, '.*')
35
+
36
+ # 'dir_with_name' is the original filename WITHOUT the extension
37
+ dir_with_name = "#{dirname}/#{basename}"
38
+ dir_with_name = basename if dirname == '.'
39
+
40
+ extension = filename.gsub(dir_with_name, '')
41
+ timestamp = Time.now.strftime("%Y-%m-%dT%H%M%S")
42
+ "#{dirname}/#{basename}_#{timestamp}#{extension}"
43
+ end
44
+
45
+ def config_error(config_name)
46
+ Error.new("Storage adapter configuration requires #{config_name}. Details here: https://github.com/fdoxyz/martilla")
47
+ end
48
+
49
+ # When a new Storage is supported it needs to go here
50
+ def self.create(config = {})
9
51
  case config['type'].downcase
10
52
  when 'local'
11
- @storage = Storages::Local.new(config['options'])
53
+ Local.new(config['options'])
12
54
  when 's3'
13
- @storage = Storages::S3.new(config['options'])
55
+ S3.new(config['options'])
14
56
  when 'scp'
15
- @storage = Storages::Scp.new(config['options'])
57
+ Scp.new(config['options'])
16
58
  else
17
59
  raise Error.new("Invalid Storage type: #{config['type']}")
18
60
  end
19
61
  end
20
-
21
- def_delegators :@storage, :persist
22
62
  end
23
63
  end
@@ -1,5 +1,9 @@
1
- class Martilla::Storages::Local < Martilla::Storages::Base
2
- def persist(filepath, opts)
3
-
1
+ module Martilla
2
+ class Local < Storage
3
+ def persist(tmp_file:, gzip:)
4
+ `mv #{tmp_file} #{output_filename(gzip)}`
5
+ return nil if $?.success?
6
+ raise Error.new("Local storage failed with code #{$?.exitstatus}")
7
+ end
4
8
  end
5
9
  end
@@ -1,5 +1,42 @@
1
- class Martilla::Storages::S3 < Martilla::Storages::Base
2
- def persist(filepath, opts)
1
+ require 'aws-sdk-s3'
3
2
 
3
+ module Martilla
4
+ class S3 < Storage
5
+ def persist(tmp_file:, gzip:)
6
+ path = output_filename(gzip)
7
+ obj = s3_resource.bucket(bucket_name).object(path)
8
+
9
+ # Upload it
10
+ return nil if obj.upload_file(tmp_file)
11
+ raise Error.new('Error uploading backup to bucket')
12
+ end
13
+
14
+ private
15
+
16
+ def s3_resource
17
+ options = {}
18
+ options[:region] = aws_region unless aws_region.nil?
19
+ options[:access_key_id] = aws_access_key unless aws_access_key.nil?
20
+ options[:secret_access_key] = aws_secret_key unless aws_secret_key.nil?
21
+ Aws::S3::Resource.new(options)
22
+ end
23
+
24
+ def aws_region
25
+ @options['region']
26
+ end
27
+
28
+ def aws_access_key
29
+ @options['access_key_id']
30
+ end
31
+
32
+ def aws_secret_key
33
+ @options['secret_access_key']
34
+ end
35
+
36
+ def bucket_name
37
+ bucket = @options['bucket']
38
+ raise config_error('bucket') if bucket.nil?
39
+ bucket
40
+ end
4
41
  end
5
42
  end
@@ -1,5 +1,29 @@
1
- class Martilla::Storages::Scp < Martilla::Storages::Base
2
- def persist(filepath, opts)
1
+ module Martilla
2
+ class Scp < Storage
3
+ def persist(tmp_file:, gzip:)
4
+ `scp -i #{identity_file} #{user}@#{host}:#{output_filename(gzip)}`
5
+ return nil if $?.success?
6
+ raise Error.new("SCP storage failed with code #{$?.exitstatus}")
7
+ end
3
8
 
9
+ private
10
+
11
+ def host
12
+ scp_host = @options['host']
13
+ raise config_error('host') if scp_host.nil?
14
+ scp_host
15
+ end
16
+
17
+ def user
18
+ scp_user = @options['user']
19
+ raise config_error('user') if scp_user.nil?
20
+ scp_user
21
+ end
22
+
23
+ def identity_file
24
+ file = @options['identity_file']
25
+ raise config_error('identity_file') if file.nil?
26
+ file
27
+ end
4
28
  end
5
29
  end
@@ -0,0 +1,45 @@
1
+ module Martilla
2
+ class Backup
3
+ module Utilities
4
+ def duration_format(seconds)
5
+ case seconds
6
+ when 0..59
7
+ # 22s
8
+ "#{seconds}s"
9
+ when 60..3599
10
+ # 18m 19s
11
+ "#{s_to_m(seconds)}m #{seconds % 60}s"
12
+ else
13
+ # 7h 9m 51s
14
+ "#{s_to_h(seconds)}h #{s_to_m(seconds)}m #{seconds % 60}s"
15
+ end
16
+ end
17
+
18
+ def s_to_m(seconds)
19
+ (seconds / 60) % 60
20
+ end
21
+
22
+ def s_to_h(seconds)
23
+ (seconds / 3600) % 3600
24
+ end
25
+
26
+ def formatted_file_size
27
+ return if @file_size.nil?
28
+
29
+ if @file_size <= 799_999
30
+ compressed_file_size = @file_size / 2**10
31
+ formatted_size = '%.2f' % compressed_file_size
32
+ "#{formatted_size} KB"
33
+ elsif @file_size <= 799_999_999
34
+ compressed_file_size = @file_size / 2**20
35
+ formatted_size = '%.2f' % compressed_file_size
36
+ "#{formatted_size} MB"
37
+ else
38
+ compressed_file_size = @file_size / 2**30
39
+ formatted_size = '%.2f' % compressed_file_size
40
+ "#{formatted_size} GB"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module Martilla
2
- VERSION = '0.0.1'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/martilla.rb CHANGED
@@ -1,31 +1,24 @@
1
1
  require 'martilla/version'
2
2
  require 'martilla/cli'
3
3
 
4
- module Martilla
5
- class Error < StandardError; end
4
+ require 'martilla/backup'
5
+
6
+ require 'martilla/database'
7
+ require 'martilla/databases/mysql'
8
+ require 'martilla/databases/postgres'
6
9
 
7
- def execute_backup(config)
8
- puts "EXECUTING BACKUP WITH CONFIG: #{config}"
9
- db = Database.new(config['db'])
10
- storage = Storage.new(config['storage'])
11
- notifiers = config['notifiers'].map { |c| Notifier.new(c) }
10
+ require 'martilla/notifier'
11
+ require 'martilla/notifiers/email_notifier'
12
+ require 'martilla/notifiers/smtp'
13
+ require 'martilla/notifiers/sendmail'
14
+ require 'martilla/notifiers/ses'
15
+ require 'martilla/notifiers/slack'
12
16
 
13
- begin
14
- # Perform DB dump & storage of backup
15
- temp_filepath = db.dump
16
- storage.persist(temp_filepath)
17
- rescue Exception => e
18
- puts "EXCEPTION RAISED: #{e.inspect}"
19
- notifiers.each do |notifier|
20
- notifier.error(e)
21
- end
22
- else
23
- puts "SUCCESS"
24
- notifiers.each do |notifier|
25
- notifier.success
26
- end
27
- end
17
+ require 'martilla/storage'
18
+ require 'martilla/storages/local'
19
+ require 'martilla/storages/s3'
20
+ require 'martilla/storages/scp'
28
21
 
29
- File.delete(temp_filepath) if File.exist?(temp_filepath)
30
- end
22
+ module Martilla
23
+ class Error < StandardError; end
31
24
  end
data/martilla.gemspec CHANGED
@@ -28,11 +28,16 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ['lib']
30
30
 
31
- spec.add_dependency 'pony', '~> 1.13'
32
- spec.add_dependency 'aws-ses', '~> 0.6.0'
33
31
  spec.add_dependency 'thor', '~> 0.20.3'
32
+ spec.add_dependency 'memoist', '~> 0.16.0'
33
+ spec.add_dependency 'pony', '~> 1.13'
34
+ spec.add_dependency 'aws-sdk-ses', '~> 1.26'
35
+ spec.add_dependency 'aws-sdk-s3', '~> 1.49'
34
36
 
35
37
  spec.add_development_dependency 'bundler', '~> 2.0'
36
38
  spec.add_development_dependency 'rake', '~> 13.0'
37
39
  spec.add_development_dependency 'rspec', '~> 3.9'
40
+ spec.add_development_dependency 'byebug', '~> 11.0'
41
+ spec.add_development_dependency 'pry-byebug', '~> 3.7'
42
+ spec.add_development_dependency 'simplecov', '~> 0.17.1'
38
43
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: martilla
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.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-10 00:00:00.000000000 Z
11
+ date: 2019-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.20.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.20.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: memoist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.16.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.16.0
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: pony
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -25,33 +53,33 @@ dependencies:
25
53
  - !ruby/object:Gem::Version
26
54
  version: '1.13'
27
55
  - !ruby/object:Gem::Dependency
28
- name: aws-ses
56
+ name: aws-sdk-ses
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
59
  - - "~>"
32
60
  - !ruby/object:Gem::Version
33
- version: 0.6.0
61
+ version: '1.26'
34
62
  type: :runtime
35
63
  prerelease: false
36
64
  version_requirements: !ruby/object:Gem::Requirement
37
65
  requirements:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
- version: 0.6.0
68
+ version: '1.26'
41
69
  - !ruby/object:Gem::Dependency
42
- name: thor
70
+ name: aws-sdk-s3
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - "~>"
46
74
  - !ruby/object:Gem::Version
47
- version: 0.20.3
75
+ version: '1.49'
48
76
  type: :runtime
49
77
  prerelease: false
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
- version: 0.20.3
82
+ version: '1.49'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: bundler
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +122,48 @@ dependencies:
94
122
  - - "~>"
95
123
  - !ruby/object:Gem::Version
96
124
  version: '3.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: byebug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '11.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '11.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.17.1
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.17.1
97
167
  description: ''
98
168
  email:
99
169
  - fdov88@gmail.com
@@ -113,22 +183,24 @@ files:
113
183
  - bin/console
114
184
  - bin/martilla
115
185
  - bin/setup
186
+ - config.yml
116
187
  - lib/martilla.rb
188
+ - lib/martilla/backup.rb
117
189
  - lib/martilla/cli.rb
118
190
  - lib/martilla/database.rb
119
- - lib/martilla/databases/base.rb
120
191
  - lib/martilla/databases/mysql.rb
121
192
  - lib/martilla/databases/postgres.rb
122
193
  - lib/martilla/notifier.rb
123
- - lib/martilla/notifiers/base.rb
124
- - lib/martilla/notifiers/email.rb
194
+ - lib/martilla/notifiers/email_notifier.rb
195
+ - lib/martilla/notifiers/sendmail.rb
125
196
  - lib/martilla/notifiers/ses.rb
126
197
  - lib/martilla/notifiers/slack.rb
198
+ - lib/martilla/notifiers/smtp.rb
127
199
  - lib/martilla/storage.rb
128
- - lib/martilla/storages/base.rb
129
200
  - lib/martilla/storages/local.rb
130
201
  - lib/martilla/storages/s3.rb
131
202
  - lib/martilla/storages/scp.rb
203
+ - lib/martilla/utilities.rb
132
204
  - lib/martilla/version.rb
133
205
  - martilla.gemspec
134
206
  homepage: https://github.com/fdoxyz/martilla
@@ -1,11 +0,0 @@
1
- class Martilla::Databases::Base
2
- attr_reader :options
3
-
4
- def initialize(opts)
5
- @options = opts
6
- end
7
-
8
- def temp_filepath
9
- @options['tmp_file'] || '/tmp/db.sql'
10
- end
11
- end
@@ -1,7 +0,0 @@
1
- class Martilla::Notifiers::Base
2
- attr_reader :options
3
-
4
- def initialize(opts)
5
- @options = opts
6
- end
7
- end
@@ -1,5 +0,0 @@
1
- class Martilla::Notifiers::Email < Martilla::Notifiers::Base
2
- def notify(opts)
3
-
4
- end
5
- end
@@ -1,7 +0,0 @@
1
- class Martilla::Storages::Base
2
- attr_reader :options
3
-
4
- def initialize(opts)
5
- @options = opts
6
- end
7
- end