do_snapshot 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,99 +2,82 @@
2
2
  require 'date'
3
3
  require 'pony'
4
4
  require_relative 'core_ext/hash'
5
- require_relative 'log'
5
+ require_relative 'helpers'
6
6
 
7
7
  module DoSnapshot
8
8
  # Shared mailer.
9
9
  #
10
- module Mail
11
- def mailer
12
- UniversalMailer
13
- end
10
+ class Mail
11
+ include DoSnapshot::Helpers
14
12
 
15
- # UniversalMailer is module to deal with singleton methods.
16
- # Used to give classes access only for selected methods
17
- #
18
- module UniversalMailer
19
- module_function
13
+ attr_writer :mailer, :opts_default, :smtp_default
20
14
 
21
- def notify
22
- Mail.notify
23
- end
15
+ def initialize(options = {})
16
+ options.each { |key, option| send("#{key}=", option) }
24
17
  end
25
18
 
26
- class << self
27
- include DoSnapshot::Log
28
-
29
- attr_writer :mailer, :opts_default, :smtp_default
30
-
31
- def load_options(options = {})
32
- options.each { |key, option| send("#{key}=", option) }
33
- end
34
-
35
- def reset_options
36
- @opts = opts_default
37
- @smtp = smtp_default
38
- end
19
+ def reset_options
20
+ @opts = opts_default
21
+ @smtp = smtp_default
22
+ end
39
23
 
40
- def mailer
41
- @mailer ||= Pony.method(:mail)
42
- end
24
+ def mailer
25
+ @mailer ||= Pony.method(:mail)
26
+ end
43
27
 
44
- def smtp
45
- @smtp ||= smtp_default.dup
46
- end
28
+ def smtp
29
+ @smtp ||= smtp_default.dup
30
+ end
47
31
 
48
- def opts
49
- @opts ||= opts_default.dup
50
- end
32
+ def opts
33
+ @opts ||= opts_default.dup
34
+ end
51
35
 
52
- def smtp=(options)
53
- options.each_pair do |key, value|
54
- smtp[key.to_sym] = value
55
- end if options
56
- end
36
+ def smtp=(options)
37
+ options.each_pair do |key, value|
38
+ smtp[key.to_sym] = value
39
+ end if options
40
+ end
57
41
 
58
- def opts=(options)
59
- options.each_pair do |key, value|
60
- opts[key.to_sym] = value
61
- end if options
62
- end
42
+ def opts=(options)
43
+ options.each_pair do |key, value|
44
+ opts[key.to_sym] = value
45
+ end if options
46
+ end
63
47
 
64
- # Sending message via Hash params.
65
- #
66
- # Options:: --mail to:mail@somehost.com from:from@host.com --smtp address:smtp.gmail.com user_name:someuser password:somepassword
67
- #
68
- def notify
69
- setup_notify
70
- log.debug 'Sending e-mail notification.'
71
- # Look into your inbox :)
72
- mailer.call(opts)
73
- end
48
+ # Sending message via Hash params.
49
+ #
50
+ # Options:: --mail to:mail@somehost.com from:from@host.com --smtp address:smtp.gmail.com user_name:someuser password:somepassword
51
+ #
52
+ def notify
53
+ setup_notify
54
+ logger.debug 'Sending e-mail notification.'
55
+ # Look into your inbox :)
56
+ mailer.call(opts)
57
+ end
74
58
 
75
- protected
59
+ protected
76
60
 
77
- def opts_default
78
- @opts_default ||= {
79
- subject: 'Digital Ocean: maximum snapshots is reached.',
80
- body: "Please cleanup your Digital Ocean account.\nSnapshot maximum is reached.",
81
- from: 'noreply@someonelse.com',
82
- to: 'to@someonelse.com',
83
- via: :smtp
84
- }
85
- end
61
+ def opts_default
62
+ @opts_default ||= {
63
+ subject: 'Digital Ocean: maximum snapshots is reached.',
64
+ body: "Please cleanup your Digital Ocean account.\nSnapshot maximum is reached.",
65
+ from: 'noreply@someonelse.com',
66
+ to: 'to@someonelse.com',
67
+ via: :smtp
68
+ }
69
+ end
86
70
 
87
- def smtp_default
88
- @smtp_default ||= {
89
- domain: 'localhost.localdomain',
90
- port: '25'
91
- }
92
- end
71
+ def smtp_default
72
+ @smtp_default ||= {
73
+ domain: 'localhost.localdomain',
74
+ port: '25'
75
+ }
76
+ end
93
77
 
94
- def setup_notify
95
- opts[:body] = "#{opts[:body]}\n\nTrace: #{DateTime.now}\n#{Log.buffer.join("\n")}"
96
- opts[:via_options] = smtp
97
- end
78
+ def setup_notify
79
+ opts[:body] = "#{opts[:body]}\n\nTrace: #{DateTime.now}\n#{DoSnapshot.logger.buffer.join("\n")}"
80
+ opts[:via_options] = smtp
98
81
  end
99
82
  end
100
83
  end
@@ -0,0 +1,59 @@
1
+ require 'do_snapshot/cli'
2
+
3
+ module DoSnapshot
4
+ # CLI Runner
5
+ #
6
+ class Runner
7
+ def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
8
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
9
+ end
10
+
11
+ def execute! # rubocop:disable Metrics/MethodLength
12
+ exit_code = begin
13
+ run_cli
14
+ rescue DoSnapshot::NoTokenError, DoSnapshot::NoKeysError => _
15
+ do_nothing_on_shown_error
16
+ rescue StandardError => e
17
+ display_backtrace_otherwise(e)
18
+ rescue SystemExit => e
19
+ e.status
20
+ ensure
21
+ clean_before_exit
22
+ end
23
+
24
+ @kernel.exit(exit_code)
25
+ end
26
+
27
+ private
28
+
29
+ def run_cli
30
+ $stderr = @stderr
31
+ $stdin = @stdin
32
+ $stdout = @stdout
33
+
34
+ DoSnapshot::CLI.start(@argv)
35
+
36
+ 0
37
+ end
38
+
39
+ def do_nothing_on_shown_error
40
+ clean_before_exit
41
+ 1
42
+ end
43
+
44
+ def display_backtrace_otherwise(e)
45
+ b = e.backtrace
46
+ @stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
47
+ @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
48
+ 1
49
+ end
50
+
51
+ def clean_before_exit
52
+ DoSnapshot.cleanup
53
+
54
+ $stderr = STDERR
55
+ $stdin = STDIN
56
+ $stdout = STDOUT
57
+ end
58
+ end
59
+ end
@@ -2,5 +2,5 @@
2
2
  # Current version
3
3
  #
4
4
  module DoSnapshot
5
- VERSION = '0.2.2'
5
+ VERSION = '0.3.0'
6
6
  end
data/lib/do_snapshot.rb CHANGED
@@ -1,13 +1,46 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require_relative 'do_snapshot/version'
3
+ require_relative 'do_snapshot/configuration'
3
4
 
4
5
  # Used primary for creating snapshot's as backups for DigitalOcean
5
6
  #
6
7
  module DoSnapshot
8
+ class << self
9
+ attr_accessor :logger, :mailer
10
+
11
+ def configure
12
+ yield(config)
13
+ end
14
+
15
+ def reconfigure
16
+ @config = Configuration.new
17
+ yield(config)
18
+ end
19
+
20
+ def config
21
+ @config ||= Configuration.new
22
+ end
23
+
24
+ def cleanup
25
+ logger.close if logger
26
+ @logger = nil
27
+ @mailer = nil
28
+ @config = nil
29
+ end
30
+ end
31
+
7
32
  # Standard Request Exception. When we don't need droplet instance id.
8
33
  #
9
34
  class RequestError < StandardError; end
10
35
 
36
+ # Every call must have keys in environment or via params.
37
+ #
38
+ class NoKeysError < StandardError; end
39
+
40
+ # Every call must have token in environment or via params.
41
+ #
42
+ class NoTokenError < StandardError; end
43
+
11
44
  # Base Exception for cases when we need id for log and/or something actions.
12
45
  #
13
46
  class RequestActionError < RequestError
@@ -22,7 +55,7 @@ module DoSnapshot
22
55
  #
23
56
  class DropletShutdownError < RequestActionError
24
57
  def initialize(*args)
25
- Log.log :error, "Droplet id: #{args[0]} is Failed to Power Off."
58
+ DoSnapshot.logger.error "Droplet id: #{args[0]} is Failed to Power Off."
26
59
  super
27
60
  end
28
61
  end
@@ -32,7 +65,7 @@ module DoSnapshot
32
65
  #
33
66
  class SnapshotCreateError < RequestActionError
34
67
  def initialize(*args)
35
- Log.log :error, "Droplet id: #{args[0]} is Failed to Snapshot."
68
+ DoSnapshot.logger.error "Droplet id: #{args[0]} is Failed to Snapshot."
36
69
  super
37
70
  end
38
71
  end
@@ -42,7 +75,7 @@ module DoSnapshot
42
75
  #
43
76
  class DropletFindError < RequestError
44
77
  def initialize(*args)
45
- Log.log :error, 'Droplet Not Found'
78
+ DoSnapshot.logger.error 'Droplet Not Found'
46
79
  super
47
80
  end
48
81
  end
@@ -52,7 +85,7 @@ module DoSnapshot
52
85
  #
53
86
  class DropletListError < RequestError
54
87
  def initialize(*args)
55
- Log.log :error, 'Droplet Listing is failed to retrieve'
88
+ DoSnapshot.logger.error 'Droplet Listing is failed to retrieve'
56
89
  super
57
90
  end
58
91
  end
@@ -5,7 +5,6 @@ describe DoSnapshot::Adapter::Abstract do
5
5
  include_context 'spec'
6
6
 
7
7
  subject(:api) { described_class }
8
- subject(:log) { DoSnapshot::Log }
9
8
 
10
9
  describe '.initialize' do
11
10
  describe '#delay' do
@@ -20,10 +19,4 @@ describe DoSnapshot::Adapter::Abstract do
20
19
  it('with custom timeout') { expect(instance.timeout).to eq timeout }
21
20
  end
22
21
  end
23
-
24
- before(:each) do
25
- log.buffer = %w()
26
- log.verbose = false
27
- log.quiet = true
28
- end
29
22
  end
@@ -41,7 +41,7 @@ describe DoSnapshot::Adapter::Digitalocean do
41
41
 
42
42
  expect { instance.droplet(droplet_id) }
43
43
  .to raise_error
44
- expect(log.buffer)
44
+ expect(DoSnapshot.logger.buffer)
45
45
  .to include 'Droplet Not Found'
46
46
 
47
47
  expect(a_request(:get, droplet_url))
@@ -63,7 +63,7 @@ describe DoSnapshot::Adapter::Digitalocean do
63
63
  stub_droplets_fail
64
64
 
65
65
  expect { instance.droplets }.to raise_error
66
- expect(log.buffer)
66
+ expect(DoSnapshot.logger.buffer)
67
67
  .to include 'Droplet Listing is failed to retrieve'
68
68
 
69
69
  expect(a_request(:get, droplets_uri))
@@ -77,7 +77,7 @@ describe DoSnapshot::Adapter::Digitalocean do
77
77
  stub_droplet_start(droplet_id)
78
78
 
79
79
  instance.start_droplet(droplet_id)
80
- expect(log.buffer).to include 'Power On has been requested.'
80
+ expect(DoSnapshot.logger.buffer).to include 'Power On has been requested.'
81
81
 
82
82
  expect(a_request(:get, droplet_start_url))
83
83
  .to have_been_made
@@ -90,7 +90,7 @@ describe DoSnapshot::Adapter::Digitalocean do
90
90
 
91
91
  expect { instance.start_droplet(droplet_id) }
92
92
  .not_to raise_error
93
- expect(log.buffer)
93
+ expect(DoSnapshot.logger.buffer)
94
94
  .to include 'Droplet is still running.'
95
95
 
96
96
  expect(a_request(:get, droplet_url))
@@ -126,7 +126,7 @@ describe DoSnapshot::Adapter::Digitalocean do
126
126
 
127
127
  expect { instance.stop_droplet(droplet_id) }
128
128
  .to raise_error
129
- expect(log.buffer)
129
+ expect(DoSnapshot.logger.buffer)
130
130
  .to include 'Droplet id: 100823 is Failed to Power Off.'
131
131
 
132
132
  expect(a_request(:get, droplet_stop_url))
@@ -181,7 +181,7 @@ describe DoSnapshot::Adapter::Digitalocean do
181
181
  droplet = instance.droplet(droplet_id)
182
182
  expect { instance.cleanup_snapshots(droplet, 1) }
183
183
  .not_to raise_error
184
- expect(log.buffer)
184
+ expect(DoSnapshot.logger.buffer)
185
185
  .to include 'Snapshot name: mrcr.ru_2014_07_19 delete requested.'
186
186
 
187
187
  expect(a_request(:get, droplet_url))
@@ -200,7 +200,7 @@ describe DoSnapshot::Adapter::Digitalocean do
200
200
  droplet = instance.droplet(droplet_id)
201
201
  expect { instance.cleanup_snapshots(droplet, 1) }
202
202
  .not_to raise_error
203
- expect(log.buffer)
203
+ expect(DoSnapshot.logger.buffer)
204
204
  .to include 'Some Message'
205
205
 
206
206
  expect(a_request(:get, droplet_url))
@@ -212,10 +212,4 @@ describe DoSnapshot::Adapter::Digitalocean do
212
212
  end
213
213
  end
214
214
  end
215
-
216
- before(:each) do
217
- log.buffer = %w()
218
- log.verbose = false
219
- log.quiet = true
220
- end
221
215
  end
@@ -43,7 +43,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
43
43
 
44
44
  expect { instance.droplet(droplet_id) }
45
45
  .to raise_error
46
- expect(log.buffer)
46
+ expect(DoSnapshot.logger.buffer)
47
47
  .to include 'Droplet Not Found'
48
48
 
49
49
  expect(a_request(:get, droplet_url))
@@ -65,7 +65,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
65
65
  stub_droplets_fail
66
66
 
67
67
  expect { instance.droplets }.to raise_error
68
- expect(log.buffer)
68
+ expect(DoSnapshot.logger.buffer)
69
69
  .to include 'Droplet Listing is failed to retrieve'
70
70
 
71
71
  expect(a_request(:get, droplets_uri))
@@ -79,7 +79,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
79
79
  stub_droplet_start(droplet_id)
80
80
 
81
81
  instance.start_droplet(droplet_id)
82
- expect(log.buffer).to include 'Power On has been requested.'
82
+ expect(DoSnapshot.logger.buffer).to include 'Power On has been requested.'
83
83
 
84
84
  expect(a_request(:post, droplet_start_url))
85
85
  .to have_been_made
@@ -92,7 +92,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
92
92
 
93
93
  expect { instance.start_droplet(droplet_id) }
94
94
  .not_to raise_error
95
- expect(log.buffer)
95
+ expect(DoSnapshot.logger.buffer)
96
96
  .to include 'Droplet is still running.'
97
97
 
98
98
  expect(a_request(:get, droplet_url))
@@ -128,7 +128,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
128
128
 
129
129
  expect { instance.stop_droplet(droplet_id) }
130
130
  .to raise_error
131
- expect(log.buffer)
131
+ expect(DoSnapshot.logger.buffer)
132
132
  .to include 'Droplet id: 100823 is Failed to Power Off.'
133
133
 
134
134
  expect(a_request(:post, droplet_stop_url))
@@ -183,7 +183,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
183
183
  droplet = instance.droplet(droplet_id)
184
184
  expect { instance.cleanup_snapshots(droplet, 1) }
185
185
  .not_to raise_error
186
- expect(log.buffer)
186
+ expect(DoSnapshot.logger.buffer)
187
187
  .to include 'Snapshot: 5019770 delete requested.'
188
188
 
189
189
  expect(a_request(:get, droplet_url))
@@ -202,7 +202,7 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
202
202
  droplet = instance.droplet(droplet_id)
203
203
  expect { instance.cleanup_snapshots(droplet, 1) }
204
204
  .not_to raise_error
205
- expect(log.buffer)
205
+ expect(DoSnapshot.logger.buffer)
206
206
  .to include 'Destroy of snapshot 5019903 for droplet id: 100823 name: example.com is failed.'
207
207
 
208
208
  expect(a_request(:get, droplet_url))
@@ -214,10 +214,4 @@ describe DoSnapshot::Adapter::DigitaloceanV2 do
214
214
  end
215
215
  end
216
216
  end
217
-
218
- before(:each) do
219
- log.buffer = %w()
220
- log.verbose = false
221
- log.quiet = true
222
- end
223
217
  end
@@ -14,7 +14,7 @@ describe DoSnapshot::Command do
14
14
  it 'sends message' do
15
15
  expect { snap_runner }
16
16
  .not_to raise_error
17
- expect(log.buffer)
17
+ expect(DoSnapshot.logger.buffer)
18
18
  .to include 'All operations has been finished.'
19
19
  end
20
20
  end
@@ -108,7 +108,7 @@ describe DoSnapshot::Command do
108
108
 
109
109
  expect { cmd.fail_power_off(DoSnapshot::DropletShutdownError.new(droplet_id)) }
110
110
  .not_to raise_error
111
- expect(log.buffer)
111
+ expect(DoSnapshot.logger.buffer)
112
112
  .to include 'Power On has been requested.'
113
113
  end
114
114
 
@@ -117,9 +117,9 @@ describe DoSnapshot::Command do
117
117
 
118
118
  expect { cmd.fail_power_off(DoSnapshot::DropletShutdownError.new(droplet_id)) }
119
119
  .to raise_error
120
- expect(log.buffer)
120
+ expect(DoSnapshot.logger.buffer)
121
121
  .to include 'Droplet id: 100823 is Failed to Power Off.'
122
- expect(log.buffer)
122
+ expect(DoSnapshot.logger.buffer)
123
123
  .to include 'Droplet Not Found'
124
124
  end
125
125
 
@@ -130,16 +130,16 @@ describe DoSnapshot::Command do
130
130
 
131
131
  expect { cmd.fail_power_off(DoSnapshot::DropletShutdownError.new(droplet_id)) }
132
132
  .not_to raise_error
133
- expect(log.buffer)
133
+ expect(DoSnapshot.logger.buffer)
134
134
  .to include 'Power On failed to request.'
135
135
  end
136
136
  end
137
137
 
138
138
  before(:each) do
139
139
  stub_all_api(nil, true)
140
- log.buffer = %w()
141
- log.verbose = false
142
- log.quiet = true
140
+ DoSnapshot.logger.buffer = %w()
141
+ DoSnapshot.logger.verbose = false
142
+ DoSnapshot.logger.quiet = true
143
143
  end
144
144
 
145
145
  def load_options(options = nil)
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe DoSnapshot::Configuration do
4
+ subject(:cli) { described_class }
5
+
6
+ it { expect(cli.new).to respond_to(:logger) }
7
+ it { expect(cli.new).to respond_to(:logger_level) }
8
+ it { expect(cli.new).to respond_to(:verbose) }
9
+ it { expect(cli.new).to respond_to(:quiet) }
10
+ it { expect(cli.new).to respond_to(:mailer) }
11
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe DoSnapshot::Log do
5
+ include_context 'spec'
6
+
7
+ subject(:log) { described_class }
8
+
9
+ describe 'will have message' do
10
+ it '#info' do
11
+ expect(DoSnapshot.logger).to respond_to(:info)
12
+ DoSnapshot.logger.info('fff')
13
+ expect(DoSnapshot.logger.buffer).to include('fff')
14
+ end
15
+
16
+ it '#debug' do
17
+ expect(DoSnapshot.logger).to respond_to(:debug)
18
+ DoSnapshot.logger.info('fff')
19
+ expect(DoSnapshot.logger.buffer).to include('fff')
20
+ end
21
+
22
+ it '#warn' do
23
+ expect(DoSnapshot.logger).to respond_to(:warn)
24
+ DoSnapshot.logger.info('fff')
25
+ expect(DoSnapshot.logger.buffer).to include('fff')
26
+ end
27
+
28
+ it '#fatal' do
29
+ expect(DoSnapshot.logger).to respond_to(:fatal)
30
+ DoSnapshot.logger.info('fff')
31
+ expect(DoSnapshot.logger.buffer).to include('fff')
32
+ end
33
+
34
+ it '#error' do
35
+ expect(DoSnapshot.logger).to respond_to(:error)
36
+ DoSnapshot.logger.info('fff')
37
+ expect(DoSnapshot.logger.buffer).to include('fff')
38
+ end
39
+
40
+ it '#blablabla' do
41
+ expect(DoSnapshot.logger).not_to respond_to(:blablabla)
42
+ end
43
+
44
+ before :each do
45
+ DoSnapshot.configure do |config|
46
+ config.logger = Logger.new(log_path)
47
+ config.verbose = true
48
+ config.quiet = true
49
+ end
50
+ DoSnapshot.logger = DoSnapshot::Log.new
51
+ end
52
+ end
53
+
54
+ describe 'will work with files' do
55
+ it 'with file' do
56
+ FileUtils.remove_file(log_path, true)
57
+
58
+ DoSnapshot.configure do |config|
59
+ config.logger = Logger.new(log_path)
60
+ config.verbose = true
61
+ config.quiet = true
62
+ end
63
+ DoSnapshot.logger = DoSnapshot::Log.new
64
+
65
+ expect(File.exist?(log_path)).to be_truthy
66
+ end
67
+
68
+ it 'with no file' do
69
+ FileUtils.remove_file(log_path, true)
70
+
71
+ DoSnapshot.logger = DoSnapshot::Log.new
72
+
73
+ expect(File.exist?(log_path)).to be_falsey
74
+ end
75
+
76
+ it 'with no file but logging' do
77
+ FileUtils.remove_file(log_path, true)
78
+
79
+ DoSnapshot.logger = DoSnapshot::Log.new
80
+
81
+ expect(File.exist?(log_path)).to be_falsey
82
+
83
+ expect(DoSnapshot.logger).to respond_to(:info)
84
+ DoSnapshot.logger.info('fff')
85
+ expect(DoSnapshot.logger.buffer).to include('fff')
86
+ end
87
+ end
88
+ end
@@ -4,12 +4,26 @@ require 'spec_helper'
4
4
  describe DoSnapshot::Mail do
5
5
  include_context 'spec'
6
6
 
7
+ subject(:mail) { described_class }
8
+
7
9
  describe 'will send mail with options' do
8
10
  it '#notify' do
9
- DoSnapshot::Mail.reset_options
10
- DoSnapshot::Mail.load_options(opts: mail_options, smtp: smtp_options)
11
- expect { DoSnapshot::Mail.notify }.not_to raise_error
12
- expect(DoSnapshot::Mail.smtp[:address]).to eq(smtp_options[:address])
11
+ mail = DoSnapshot.mailer.notify
12
+ expect(mail).not_to be_falsey
13
+ expect(mail.delivery_method.settings[:address]).to eq(smtp_options[:address])
14
+ expect(mail.delivery_method.settings[:port]).to eq(smtp_options[:port])
15
+ expect(mail.delivery_method.settings[:user_name]).to eq(smtp_options[:user_name])
16
+ expect(mail.delivery_method.settings[:password]).to eq(smtp_options[:password])
17
+ expect(mail.header.fields).to include(::Mail::Field.new('From', mail_options[:from], 'UTF-8'))
18
+ expect(mail.header.fields).to include(::Mail::Field.new('To', mail_options[:to], 'UTF-8'))
19
+ end
20
+
21
+ before :each do
22
+ DoSnapshot.configure do |config|
23
+ config.mailer = mail.new(opts: mail_options, smtp: smtp_options)
24
+ end
25
+ DoSnapshot.mailer = DoSnapshot.config.mailer
26
+ DoSnapshot.logger = DoSnapshot::Log.new
13
27
  end
14
28
  end
15
29
  end