do_snapshot 0.0.6 → 0.0.7
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 +13 -5
- data/.gitignore +5 -2
- data/.rubocop.yml +3 -0
- data/.travis.yml +10 -0
- data/README.md +16 -0
- data/Rakefile +6 -0
- data/bin/do_snapshot +4 -1
- data/do_snapshot.gemspec +10 -4
- data/lib/do_snapshot.rb +47 -104
- data/lib/do_snapshot/api.rb +140 -0
- data/lib/do_snapshot/cli.rb +115 -48
- data/lib/do_snapshot/command.rb +71 -102
- data/lib/do_snapshot/core_ext/hash.rb +3 -2
- data/lib/do_snapshot/log.rb +59 -0
- data/lib/do_snapshot/mail.rb +70 -0
- data/lib/do_snapshot/version.rb +2 -1
- data/{test → log}/.keep +0 -0
- data/spec/.keep +0 -0
- data/spec/do_snapshot/api_spec.rb +218 -0
- data/spec/do_snapshot/cli_spec.rb +173 -0
- data/spec/do_snapshot/command_spec.rb +121 -0
- data/spec/do_snapshots_spec.rb +58 -0
- data/spec/fixtures/error_message.json +4 -0
- data/spec/fixtures/response_event.json +4 -0
- data/spec/fixtures/show_droplet.json +39 -0
- data/spec/fixtures/show_droplet_inactive.json +39 -0
- data/spec/fixtures/show_droplets.json +35 -0
- data/spec/fixtures/show_droplets_empty.json +4 -0
- data/spec/fixtures/show_event_done.json +10 -0
- data/spec/fixtures/show_event_start.json +10 -0
- data/spec/shared/api_helpers.rb +117 -0
- data/spec/shared/environment.rb +114 -0
- data/spec/shared/uri_helpers.rb +12 -0
- data/spec/spec_helper.rb +34 -0
- data/tmp/.keep +0 -0
- metadata +145 -23
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
# Rails symbolize keys methods for Hash
|
2
3
|
#
|
3
4
|
class Hash
|
@@ -25,12 +26,12 @@ class Hash
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def symbolize_keys
|
28
|
-
transform_keys { |key| key.to_sym rescue key }
|
29
|
+
transform_keys { |key| key.to_sym rescue key } # rubocop:disable Style/RescueModifier
|
29
30
|
end
|
30
31
|
|
31
32
|
# Destructively convert all keys to symbols, as long as they respond
|
32
33
|
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
33
34
|
def symbolize_keys!
|
34
|
-
transform_keys! { |key| key.to_sym rescue key }
|
35
|
+
transform_keys! { |key| key.to_sym rescue key } # rubocop:disable Style/RescueModifier
|
35
36
|
end
|
36
37
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module DoSnapshot
|
5
|
+
# Shared logger
|
6
|
+
#
|
7
|
+
class Log
|
8
|
+
class << self
|
9
|
+
attr_accessor :logger, :shell, :quiet, :verbose
|
10
|
+
attr_writer :buffer
|
11
|
+
|
12
|
+
def buffer
|
13
|
+
@buffer ||= %w()
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(message)
|
17
|
+
log :info, message
|
18
|
+
end
|
19
|
+
|
20
|
+
def warning(message)
|
21
|
+
log :warn, message
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message)
|
25
|
+
log :error, message
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug(message)
|
29
|
+
log :debug, message
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def log(type, message)
|
35
|
+
buffer << message
|
36
|
+
logger.send(type, message) if logger
|
37
|
+
|
38
|
+
say message, color(type) unless type == :debug && !verbose
|
39
|
+
end
|
40
|
+
|
41
|
+
def say(message, color)
|
42
|
+
shell.say message, color if shell
|
43
|
+
end
|
44
|
+
|
45
|
+
def color(type)
|
46
|
+
case type
|
47
|
+
when :debug
|
48
|
+
:white
|
49
|
+
when :error
|
50
|
+
:red
|
51
|
+
when :warn
|
52
|
+
:yellow
|
53
|
+
else
|
54
|
+
:green
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'date'
|
3
|
+
require 'pony'
|
4
|
+
require 'do_snapshot/core_ext/hash'
|
5
|
+
|
6
|
+
module DoSnapshot
|
7
|
+
# Shared mailer.
|
8
|
+
#
|
9
|
+
class Mail
|
10
|
+
class << self
|
11
|
+
attr_accessor :opts
|
12
|
+
attr_writer :smtp, :opts_default, :smtp_default
|
13
|
+
|
14
|
+
def smtp
|
15
|
+
@smtp ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sending message via Hash params.
|
19
|
+
#
|
20
|
+
# Options:: --mail to:mail@somehost.com from:from@host.com --smtp address:smtp.gmail.com user_name:someuser password:somepassword
|
21
|
+
#
|
22
|
+
def notify
|
23
|
+
return unless opts
|
24
|
+
|
25
|
+
opts.symbolize_keys!
|
26
|
+
smtp.symbolize_keys!
|
27
|
+
|
28
|
+
opts_setup
|
29
|
+
smtp_setup
|
30
|
+
|
31
|
+
Log.debug 'Sending e-mail notification.'
|
32
|
+
# Look into your inbox :)
|
33
|
+
Pony.mail(opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def opts_default
|
39
|
+
@opts_default ||= {
|
40
|
+
subject: 'Digital Ocean: maximum snapshots is reached.',
|
41
|
+
body: "Please cleanup your Digital Ocean account.\nSnapshot maximum is reached.",
|
42
|
+
from: 'noreply@someonelse.com',
|
43
|
+
to: 'to@someonelse.com',
|
44
|
+
via: :smtp
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def smtp_default
|
49
|
+
@smtp_default ||= {
|
50
|
+
domain: 'localhost.localdomain',
|
51
|
+
port: '25'
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def opts_setup
|
56
|
+
opts_default.each_pair do |key, value|
|
57
|
+
opts[key] = value unless opts.include? key
|
58
|
+
end
|
59
|
+
opts[:body] = "#{opts[:body]}\n\nTrace: #{DateTime.now}\n#{Log.buffer.join("\n")}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def smtp_setup
|
63
|
+
smtp_default.each_pair do |key, value|
|
64
|
+
smtp[key] = value unless smtp.include? key
|
65
|
+
end
|
66
|
+
opts[:via_options] = smtp
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/do_snapshot/version.rb
CHANGED
data/{test → log}/.keep
RENAMED
File without changes
|
data/spec/.keep
ADDED
File without changes
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe DoSnapshot::API do
|
5
|
+
include_context 'spec'
|
6
|
+
|
7
|
+
subject(:api) { described_class }
|
8
|
+
subject(:log) { DoSnapshot::Log }
|
9
|
+
|
10
|
+
describe '.initialize' do
|
11
|
+
describe '#delay' do
|
12
|
+
let(:delay) { 5 }
|
13
|
+
let(:instance) { api.new(delay: delay) }
|
14
|
+
it('with custom delay') { expect(instance.delay).to eq delay }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#timeout' do
|
18
|
+
let(:timeout) { 5 }
|
19
|
+
let(:instance) { api.new(timeout: timeout) }
|
20
|
+
it('with custom timeout') { expect(instance.timeout).to eq timeout }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'droplets' do
|
25
|
+
let(:instance) { api.new(delay: delay, timeout: timeout) }
|
26
|
+
include_context 'uri_helpers'
|
27
|
+
|
28
|
+
describe '.droplet' do
|
29
|
+
it 'with droplet' do
|
30
|
+
stub_droplet(droplet_id)
|
31
|
+
|
32
|
+
instance.droplet(droplet_id)
|
33
|
+
|
34
|
+
expect(a_request(:get, droplet_url))
|
35
|
+
.to have_been_made
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'with error' do
|
39
|
+
stub_droplet_fail(droplet_id)
|
40
|
+
|
41
|
+
expect { instance.droplet(droplet_id) }
|
42
|
+
.to raise_error
|
43
|
+
expect(log.buffer)
|
44
|
+
.to include 'Droplet Not Found'
|
45
|
+
|
46
|
+
expect(a_request(:get, droplet_url))
|
47
|
+
.to have_been_made
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '.droplets' do
|
52
|
+
it 'with droplets' do
|
53
|
+
stub_droplets
|
54
|
+
|
55
|
+
instance.droplets
|
56
|
+
|
57
|
+
expect(a_request(:get, droplets_uri))
|
58
|
+
.to have_been_made
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'with error' do
|
62
|
+
stub_droplets_fail
|
63
|
+
|
64
|
+
expect { instance.droplets }.to raise_error
|
65
|
+
expect(log.buffer)
|
66
|
+
.to include 'Droplet Listing is failed to retrieve'
|
67
|
+
|
68
|
+
expect(a_request(:get, droplets_uri))
|
69
|
+
.to have_been_made
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '.start_droplet' do
|
74
|
+
it 'with event' do
|
75
|
+
stub_droplet_inactive(droplet_id)
|
76
|
+
stub_droplet_start(droplet_id)
|
77
|
+
|
78
|
+
instance.start_droplet(droplet_id)
|
79
|
+
expect(log.buffer).to include 'Power On has been requested.'
|
80
|
+
|
81
|
+
expect(a_request(:get, droplet_start_url))
|
82
|
+
.to have_been_made
|
83
|
+
expect(a_request(:get, droplet_url))
|
84
|
+
.to have_been_made
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'with warning message' do
|
88
|
+
stub_droplet(droplet_id)
|
89
|
+
|
90
|
+
expect { instance.start_droplet(droplet_id) }
|
91
|
+
.not_to raise_error
|
92
|
+
expect(log.buffer)
|
93
|
+
.to include 'Droplet is still running.'
|
94
|
+
|
95
|
+
expect(a_request(:get, droplet_url))
|
96
|
+
.to have_been_made
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'with error' do
|
100
|
+
stub_droplet_fail(droplet_id)
|
101
|
+
|
102
|
+
expect { instance.start_droplet(droplet_id) }
|
103
|
+
.to raise_error
|
104
|
+
|
105
|
+
expect(a_request(:get, droplet_url))
|
106
|
+
.to have_been_made
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '.stop_droplet' do
|
111
|
+
it 'with event' do
|
112
|
+
stub_event_done(event_id)
|
113
|
+
stub_droplet_stop(droplet_id)
|
114
|
+
|
115
|
+
instance.stop_droplet(droplet_id)
|
116
|
+
|
117
|
+
expect(a_request(:get, droplet_stop_url))
|
118
|
+
.to have_been_made
|
119
|
+
expect(a_request(:get, event_find_url))
|
120
|
+
.to have_been_made
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'with error' do
|
124
|
+
stub_droplet_stop_fail(droplet_id)
|
125
|
+
|
126
|
+
expect { instance.stop_droplet(droplet_id) }
|
127
|
+
.to raise_error
|
128
|
+
expect(log.buffer)
|
129
|
+
.to include 'Droplet id: 100823 is Failed to Power Off.'
|
130
|
+
|
131
|
+
expect(a_request(:get, droplet_stop_url))
|
132
|
+
.to have_been_made
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '.create_snapshot' do
|
137
|
+
it 'with success' do
|
138
|
+
stub_event_done(event_id)
|
139
|
+
stub_droplet_snapshot(droplet_id, snapshot_name)
|
140
|
+
|
141
|
+
expect { instance.create_snapshot(droplet_id, snapshot_name) }
|
142
|
+
.not_to raise_error
|
143
|
+
|
144
|
+
expect(a_request(:get, droplet_snapshot_url))
|
145
|
+
.to have_been_made
|
146
|
+
expect(a_request(:get, event_find_url))
|
147
|
+
.to have_been_made
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'with error' do
|
151
|
+
stub_droplet_snapshot_fail(droplet_id, snapshot_name)
|
152
|
+
|
153
|
+
expect { instance.create_snapshot(droplet_id, snapshot_name) }
|
154
|
+
.to raise_error
|
155
|
+
|
156
|
+
expect(a_request(:get, droplet_snapshot_url))
|
157
|
+
.to have_been_made
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'with event error' do
|
161
|
+
stub_droplet_snapshot(droplet_id, snapshot_name)
|
162
|
+
stub_event_fail(event_id)
|
163
|
+
|
164
|
+
expect { instance.create_snapshot(droplet_id, snapshot_name) }
|
165
|
+
.to raise_error
|
166
|
+
|
167
|
+
expect(a_request(:get, droplet_snapshot_url))
|
168
|
+
.to have_been_made
|
169
|
+
expect(a_request(:get, event_find_url))
|
170
|
+
.to have_been_made
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '.cleanup_snapshots' do
|
175
|
+
it 'with success' do
|
176
|
+
stub_droplet(droplet_id)
|
177
|
+
stub_image_destroy(image_id)
|
178
|
+
stub_image_destroy(image_id2)
|
179
|
+
|
180
|
+
droplet = instance.droplet(droplet_id)
|
181
|
+
expect { instance.cleanup_snapshots(droplet.droplet, 1) }
|
182
|
+
.not_to raise_error
|
183
|
+
expect(log.buffer)
|
184
|
+
.to include 'Snapshot name: mrcr.ru_2014_07_19 delete requested.'
|
185
|
+
|
186
|
+
expect(a_request(:get, droplet_url))
|
187
|
+
.to have_been_made
|
188
|
+
expect(a_request(:get, image_destroy_url))
|
189
|
+
.to have_been_made
|
190
|
+
expect(a_request(:get, image_destroy2_url))
|
191
|
+
.to have_been_made
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'with warning message' do
|
195
|
+
stub_droplet(droplet_id)
|
196
|
+
stub_image_destroy_fail(image_id)
|
197
|
+
stub_image_destroy_fail(image_id2)
|
198
|
+
|
199
|
+
droplet = instance.droplet(droplet_id)
|
200
|
+
expect { instance.cleanup_snapshots(droplet.droplet, 1) }
|
201
|
+
.not_to raise_error
|
202
|
+
expect(log.buffer)
|
203
|
+
.to include 'Some Message'
|
204
|
+
|
205
|
+
expect(a_request(:get, droplet_url))
|
206
|
+
.to have_been_made
|
207
|
+
expect(a_request(:get, image_destroy_url))
|
208
|
+
.to have_been_made
|
209
|
+
expect(a_request(:get, image_destroy2_url))
|
210
|
+
.to have_been_made
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
before(:each) do
|
216
|
+
log.buffer = %w()
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe DoSnapshot::CLI do
|
5
|
+
include_context 'spec'
|
6
|
+
|
7
|
+
subject(:cli) { described_class }
|
8
|
+
subject(:command) { DoSnapshot::Command }
|
9
|
+
subject(:api) { DoSnapshot::API }
|
10
|
+
|
11
|
+
describe '.snap' do
|
12
|
+
|
13
|
+
it 'with exclude' do
|
14
|
+
excluded_droplets = %w( 100824 )
|
15
|
+
stub_all_api(%w(100825 100823))
|
16
|
+
hash_attribute_eq_no_stub(exclude: excluded_droplets, only: %w())
|
17
|
+
|
18
|
+
expect(command.send('exclude'))
|
19
|
+
.to eq excluded_droplets
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'with only' do
|
23
|
+
selected_droplets = %w( 100823 )
|
24
|
+
stub_all_api(selected_droplets)
|
25
|
+
hash_attribute_eq_no_stub(only: selected_droplets)
|
26
|
+
|
27
|
+
expect(command.send('only'))
|
28
|
+
.to eq selected_droplets
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'with 1 delay' do
|
32
|
+
set_api_attribute(delay: 1, timeout: timeout)
|
33
|
+
attribute_eq 'delay', 1
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'with 0 delay' do
|
37
|
+
set_api_attribute(delay: 0, timeout: timeout)
|
38
|
+
attribute_eq 'delay', 0
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'with custom timeout' do
|
42
|
+
set_api_attribute(timeout: 1, delay: delay)
|
43
|
+
attribute_eq 'timeout', 1
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'with keep' do
|
47
|
+
attribute_eq 'keep', 7
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'with quiet' do
|
51
|
+
attribute_eq 'quiet', true
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'with no quiet' do
|
55
|
+
attribute_eq 'quiet', false
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'with stop' do
|
59
|
+
attribute_eq 'stop', true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'with no stop' do
|
63
|
+
attribute_eq 'stop', false
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'with clean' do
|
67
|
+
attribute_eq 'clean', true
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'with no clean' do
|
71
|
+
attribute_eq 'clean', false
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'with digital ocean credentials' do
|
75
|
+
with_hash_attribute_eq(cli_keys)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'with no digital ocean credentials' do
|
79
|
+
without_hash_attribute_eq(cli_keys)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'with mail' do
|
83
|
+
hash_attribute_eq(mail_options)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'with no mail' do
|
87
|
+
without_hash_attribute_eq(mail_options)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'with smtp' do
|
91
|
+
hash_attribute_eq(smtp_options)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'with no smtp' do
|
95
|
+
without_hash_attribute_eq(smtp_options)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'with log' do
|
99
|
+
hash_attribute_eq(log)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'with no log' do
|
103
|
+
without_hash_attribute_eq(log)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '.version' do
|
108
|
+
it 'shows the correct version' do
|
109
|
+
@cli.options = @cli.options.merge(version: true)
|
110
|
+
@cli.version
|
111
|
+
|
112
|
+
expect($stdout.string.chomp)
|
113
|
+
.to eq("#{DoSnapshot::VERSION}")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '.help' do
|
118
|
+
it 'shows a help message' do
|
119
|
+
@cli.help
|
120
|
+
expect($stdout.string)
|
121
|
+
.to match('Commands:')
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'shows a help message for specific commands' do
|
125
|
+
@cli.help 'snap'
|
126
|
+
expect($stdout.string)
|
127
|
+
.to match('Usage:')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def attribute_eq(name, value)
|
132
|
+
stub_all_api
|
133
|
+
options = default_options.merge!(:"#{name}" => value)
|
134
|
+
@cli.options = @cli.options.merge(options)
|
135
|
+
@cli.snap
|
136
|
+
|
137
|
+
expect(command.send(name))
|
138
|
+
.to eq value
|
139
|
+
end
|
140
|
+
|
141
|
+
def hash_attribute_eq(hash)
|
142
|
+
stub_all_api
|
143
|
+
options = default_options.merge!(hash)
|
144
|
+
@cli.options = @cli.options.merge(options)
|
145
|
+
@cli.snap
|
146
|
+
end
|
147
|
+
|
148
|
+
def with_hash_attribute_eq(hash)
|
149
|
+
hash_attribute_eq hash
|
150
|
+
expect(@cli.options)
|
151
|
+
.to include(hash)
|
152
|
+
end
|
153
|
+
|
154
|
+
def without_hash_attribute_eq(hash)
|
155
|
+
hash_attribute_eq({})
|
156
|
+
expect(@cli.options)
|
157
|
+
.not_to include(hash)
|
158
|
+
end
|
159
|
+
|
160
|
+
def hash_attribute_eq_no_stub(hash)
|
161
|
+
options = default_options.merge!(hash)
|
162
|
+
@cli.options = @cli.options.merge(options)
|
163
|
+
@cli.snap
|
164
|
+
end
|
165
|
+
|
166
|
+
def set_api_attribute(options = { delay: delay, timeout: timeout }) # rubocop:disable Style/AccessorMethodName
|
167
|
+
command.send('api=', api.new(options))
|
168
|
+
end
|
169
|
+
|
170
|
+
after(:each) do
|
171
|
+
# stub_cleanup
|
172
|
+
end
|
173
|
+
end
|