do_snapshot 0.0.12 → 0.0.13
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 +4 -4
- data/README.md +3 -2
- data/lib/do_snapshot/api.rb +11 -7
- data/lib/do_snapshot/cli.rb +14 -9
- data/lib/do_snapshot/command.rb +72 -71
- data/lib/do_snapshot/mail.rb +1 -1
- data/lib/do_snapshot/version.rb +1 -1
- data/lib/do_snapshot.rb +1 -1
- data/spec/do_snapshot/api_spec.rb +1 -1
- data/spec/do_snapshot/cli_spec.rb +0 -1
- data/spec/do_snapshot/command_spec.rb +49 -4
- data/spec/do_snapshots_spec.rb +0 -1
- data/spec/shared/api_helpers.rb +0 -1
- data/spec/spec_helper.rb +3 -4
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd8a67c728fd77180645b3ea87ee37db50ab627e
|
|
4
|
+
data.tar.gz: e3196ed56ad208a890c3be2f55bb02cb7499a06f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f91dd0dc2e68e03eb59a48e7e2077893d2d4574d6f2fc2900070adcaa1c9c580b9d001ece9283d34da67fdbe4ec93e0ed8894d0f74e446b774f3837ed4f1a44
|
|
7
|
+
data.tar.gz: 7a27ff1d46bc31ffd257ae77f0e474408172d4dc6dd755615215dedf280e842f88c1fe543b099ca522bf552a8ec070744d4d7ee661a712638e9d0211495aafbb
|
data/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
# DoSnapshot CLI
|
|
1
|
+
# DoSnapshot CLI
|
|
2
|
+
[Project Page at Digital Ocean](https://www.digitalocean.com/community/projects/dosnapshot), comment or vote for this project.
|
|
2
3
|
|
|
3
4
|
[](http://badge.fury.io/rb/do_snapshot)
|
|
4
5
|
[](https://travis-ci.org/merqlove/do_snapshot)
|
|
@@ -183,4 +184,4 @@ For working mailer you need to set e-mail settings via run options.
|
|
|
183
184
|
|
|
184
185
|
Copyright (c) 2014 Alexander Merkulov
|
|
185
186
|
|
|
186
|
-
MIT License
|
|
187
|
+
MIT License
|
data/lib/do_snapshot/api.rb
CHANGED
|
@@ -87,13 +87,7 @@ module DoSnapshot
|
|
|
87
87
|
snapshot = instance.snapshots[i]
|
|
88
88
|
event = Digitalocean::Image.destroy(snapshot.id)
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
Log.error "Destroy of snapshot #{snapshot.name} for droplet id: #{instance.id} name: #{instance.name} is failed."
|
|
92
|
-
elsif event && !event.status.include?('OK')
|
|
93
|
-
Log.error event.message
|
|
94
|
-
else
|
|
95
|
-
Log.debug "Snapshot name: #{snapshot.name} delete requested."
|
|
96
|
-
end
|
|
90
|
+
after_cleanup(instance.id, instance.name, snapshot, event)
|
|
97
91
|
end
|
|
98
92
|
end
|
|
99
93
|
|
|
@@ -136,5 +130,15 @@ module DoSnapshot
|
|
|
136
130
|
Log.error 'Power On failed to request.'
|
|
137
131
|
end
|
|
138
132
|
end
|
|
133
|
+
|
|
134
|
+
def after_cleanup(droplet_id, droplet_name, snapshot, event)
|
|
135
|
+
if !event
|
|
136
|
+
Log.error "Destroy of snapshot #{snapshot.name} for droplet id: #{droplet_id} name: #{droplet_name} is failed."
|
|
137
|
+
elsif event && !event.status.include?('OK')
|
|
138
|
+
Log.error event.message
|
|
139
|
+
else
|
|
140
|
+
Log.debug "Snapshot name: #{snapshot.name} delete requested."
|
|
141
|
+
end
|
|
142
|
+
end
|
|
139
143
|
end
|
|
140
144
|
end
|
data/lib/do_snapshot/cli.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
|
2
2
|
require 'thor'
|
|
3
3
|
require 'do_snapshot'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
require_relative 'command'
|
|
5
|
+
require_relative 'mail'
|
|
6
|
+
require_relative 'log'
|
|
7
7
|
|
|
8
8
|
module DoSnapshot
|
|
9
9
|
# CLI is here
|
|
@@ -137,16 +137,13 @@ module DoSnapshot
|
|
|
137
137
|
desc: 'DIGITAL_OCEAN_API_KEY. if you can\'t use environment.'
|
|
138
138
|
|
|
139
139
|
def snap
|
|
140
|
-
Command.
|
|
140
|
+
Command.load_options options, %w( log mail smtp trace digital_ocean_client_id digital_ocean_api_key )
|
|
141
|
+
Command.snap
|
|
141
142
|
rescue => e
|
|
142
143
|
Command.fail_power_off(e) if [SnapshotCreateError, DropletShutdownError].include?(e.class)
|
|
143
144
|
Log.error e.message
|
|
144
145
|
backtrace(e) if options.include? 'trace'
|
|
145
|
-
|
|
146
|
-
Mail.opts[:subject] = 'Digital Ocean: Error.'
|
|
147
|
-
Mail.opts[:body] = 'Please check your droplets.'
|
|
148
|
-
Mail.notify
|
|
149
|
-
end
|
|
146
|
+
send_error
|
|
150
147
|
end
|
|
151
148
|
|
|
152
149
|
desc 'version, -V', 'Shows the version of the currently installed DoSnapshot gem'
|
|
@@ -160,6 +157,14 @@ module DoSnapshot
|
|
|
160
157
|
Mail.smtp = options['smtp']
|
|
161
158
|
end
|
|
162
159
|
|
|
160
|
+
def send_error
|
|
161
|
+
return unless Mail.opts
|
|
162
|
+
|
|
163
|
+
Mail.opts[:subject] = 'Digital Ocean: Error.'
|
|
164
|
+
Mail.opts[:body] = 'Please check your droplets.'
|
|
165
|
+
Mail.notify
|
|
166
|
+
end
|
|
167
|
+
|
|
163
168
|
def set_logger
|
|
164
169
|
Log.quiet = options['quiet']
|
|
165
170
|
Log.verbose = options['trace']
|
data/lib/do_snapshot/command.rb
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
|
2
|
-
|
|
2
|
+
require_relative 'api'
|
|
3
3
|
|
|
4
4
|
module DoSnapshot
|
|
5
5
|
# Our commands live here :)
|
|
6
6
|
#
|
|
7
7
|
class Command # rubocop:disable ClassLength
|
|
8
8
|
class << self
|
|
9
|
-
def snap
|
|
10
|
-
return unless options
|
|
11
|
-
|
|
12
|
-
options.each_pair do |key, option|
|
|
13
|
-
send("#{key}=", option) unless skip.include?(key)
|
|
14
|
-
end
|
|
15
|
-
|
|
9
|
+
def snap
|
|
16
10
|
Log.info 'Start performing operations'
|
|
17
11
|
work_with_droplets
|
|
18
12
|
Log.info 'All operations has been finished.'
|
|
@@ -20,6 +14,12 @@ module DoSnapshot
|
|
|
20
14
|
Mail.notify if notify && !quiet
|
|
21
15
|
end
|
|
22
16
|
|
|
17
|
+
def load_options(options = {}, skip = %w())
|
|
18
|
+
options.each_pair do |key, option|
|
|
19
|
+
send("#{key}=", option) unless skip.include?(key)
|
|
20
|
+
end if options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
23
|
def fail_power_off(e)
|
|
24
24
|
return unless e && e.id
|
|
25
25
|
api.start_droplet(e.id)
|
|
@@ -27,17 +27,52 @@ module DoSnapshot
|
|
|
27
27
|
raise DropletFindError, e.message, e.backtrace
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
def stop_droplet(droplet)
|
|
31
|
+
Log.debug 'Shutting down droplet.'
|
|
32
|
+
api.stop_droplet(droplet.id) unless droplet.status.include? 'off'
|
|
33
|
+
end
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
# Trying to create a snapshot.
|
|
36
|
+
#
|
|
37
|
+
def create_snapshot(droplet) # rubocop:disable MethodLength,Metrics/AbcSize
|
|
38
|
+
Log.info "Start creating snapshot for droplet id: #{droplet.id} name: #{droplet.name}."
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
today = DateTime.now
|
|
41
|
+
name = "#{droplet.name}_#{today.strftime('%Y_%m_%d')}"
|
|
42
|
+
# noinspection RubyResolve
|
|
43
|
+
snapshot_size = droplet.snapshots.size
|
|
44
|
+
|
|
45
|
+
Log.debug 'Wait until snapshot will be created.'
|
|
46
|
+
|
|
47
|
+
api.create_snapshot droplet.id, name
|
|
48
|
+
|
|
49
|
+
snapshot_size += 1
|
|
50
|
+
|
|
51
|
+
Log.info "Snapshot name: #{name} created successfully."
|
|
52
|
+
Log.info "Droplet id: #{droplet.id} name: #{droplet.name} snapshots: #{snapshot_size}."
|
|
53
|
+
|
|
54
|
+
# Cleanup snapshots.
|
|
55
|
+
cleanup_snapshots droplet, snapshot_size if clean
|
|
56
|
+
rescue => e
|
|
57
|
+
case e.class.to_s
|
|
58
|
+
when 'DoSnapshot::SnapshotCleanupError'
|
|
59
|
+
raise e.class, e.message, e.backtrace
|
|
60
|
+
else
|
|
61
|
+
raise SnapshotCreateError.new(droplet.id), e.message, e.backtrace
|
|
62
|
+
end
|
|
63
|
+
end
|
|
36
64
|
|
|
37
65
|
def api
|
|
38
66
|
@api ||= API.new(delay: delay, timeout: timeout)
|
|
39
67
|
end
|
|
40
68
|
|
|
69
|
+
protected
|
|
70
|
+
|
|
71
|
+
attr_accessor :droplets, :exclude, :only
|
|
72
|
+
attr_accessor :keep, :quiet, :stop, :clean, :timeout, :delay
|
|
73
|
+
|
|
74
|
+
attr_writer :notify, :threads, :api
|
|
75
|
+
|
|
41
76
|
def notify
|
|
42
77
|
@notify ||= false
|
|
43
78
|
end
|
|
@@ -69,12 +104,9 @@ module DoSnapshot
|
|
|
69
104
|
droplets.each do |droplet|
|
|
70
105
|
id = droplet.id.to_s
|
|
71
106
|
next if exclude.include? id
|
|
72
|
-
next
|
|
73
|
-
|
|
74
|
-
Log.debug "Droplet id: #{id} name: #{droplet.name} "
|
|
75
|
-
instance = api.droplet id
|
|
107
|
+
next unless only.empty? || only.include?(id)
|
|
76
108
|
|
|
77
|
-
|
|
109
|
+
prepare_droplet id, droplet.name
|
|
78
110
|
end
|
|
79
111
|
end
|
|
80
112
|
|
|
@@ -86,81 +118,50 @@ module DoSnapshot
|
|
|
86
118
|
|
|
87
119
|
# Run threads
|
|
88
120
|
#
|
|
89
|
-
def thread_runner(
|
|
121
|
+
def thread_runner(droplet)
|
|
90
122
|
threads << Thread.new do
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
create_snapshot instance
|
|
123
|
+
stop_droplet droplet
|
|
124
|
+
create_snapshot droplet
|
|
94
125
|
end
|
|
95
126
|
end
|
|
96
127
|
|
|
97
|
-
# Preparing
|
|
98
|
-
#
|
|
128
|
+
# Preparing droplet to take a snapshot.
|
|
129
|
+
# Droplet instance must be powered off first!
|
|
99
130
|
#
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
thread_runner(instance)
|
|
105
|
-
end
|
|
131
|
+
def prepare_droplet(id, name)
|
|
132
|
+
Log.debug "Droplet id: #{id} name: #{name} "
|
|
133
|
+
instance = api.droplet id
|
|
134
|
+
droplet = instance.droplet
|
|
106
135
|
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
if
|
|
110
|
-
|
|
111
|
-
return true if stop
|
|
112
|
-
end
|
|
113
|
-
false
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def stop_droplet(instance)
|
|
117
|
-
api.stop_droplet(instance.id) unless instance.status.include? 'off'
|
|
136
|
+
return unless droplet
|
|
137
|
+
Log.info "Preparing droplet id: #{droplet.id} name: #{droplet.name} to take snapshot."
|
|
138
|
+
return if too_much_snapshots(droplet)
|
|
139
|
+
thread_runner(droplet)
|
|
118
140
|
end
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
def create_snapshot(instance) # rubocop:disable MethodLength
|
|
123
|
-
Log.info "Start creating snapshot for droplet id: #{instance.id} name: #{instance.name}."
|
|
124
|
-
|
|
125
|
-
today = DateTime.now
|
|
126
|
-
name = "#{instance.name}_#{today.strftime('%Y_%m_%d')}"
|
|
142
|
+
def too_much_snapshots(instance)
|
|
127
143
|
# noinspection RubyResolve
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
api.create_snapshot instance.id, name
|
|
133
|
-
|
|
134
|
-
snapshot_size += 1
|
|
135
|
-
|
|
136
|
-
Log.info "Snapshot name: #{name} created successfully."
|
|
137
|
-
Log.info "Droplet id: #{instance.id} name: #{instance.name} snapshots: #{snapshot_size}."
|
|
138
|
-
|
|
139
|
-
# Cleanup snapshots.
|
|
140
|
-
cleanup_snapshots instance, snapshot_size if clean
|
|
141
|
-
rescue => e
|
|
142
|
-
case e.class.to_s
|
|
143
|
-
when 'DoSnapshot::SnapshotCleanupError'
|
|
144
|
-
raise e.class, e.message, e.backtrace
|
|
145
|
-
else
|
|
146
|
-
raise SnapshotCreateError.new(instance.id), e.message, e.backtrace
|
|
147
|
-
end
|
|
144
|
+
return false unless instance.snapshots.size >= keep
|
|
145
|
+
warning_size(instance.id, instance.name, keep)
|
|
146
|
+
stop ? true : false
|
|
148
147
|
end
|
|
149
148
|
|
|
150
149
|
# Cleanup our snapshots.
|
|
151
150
|
#
|
|
152
|
-
def cleanup_snapshots(
|
|
151
|
+
def cleanup_snapshots(droplet, size) # rubocop:disable Metrics/AbcSize
|
|
153
152
|
return unless size > keep
|
|
154
153
|
|
|
155
|
-
warning_size(
|
|
154
|
+
warning_size(droplet.id, droplet.name, size)
|
|
156
155
|
|
|
157
|
-
Log.debug "Cleaning up snapshots for droplet id: #{
|
|
156
|
+
Log.debug "Cleaning up snapshots for droplet id: #{droplet.id} name: #{droplet.name}."
|
|
158
157
|
|
|
159
|
-
api.cleanup_snapshots(
|
|
158
|
+
api.cleanup_snapshots(droplet, size - keep - 1)
|
|
160
159
|
rescue => e
|
|
161
160
|
raise SnapshotCleanupError, e.message, e.backtrace
|
|
162
161
|
end
|
|
163
162
|
|
|
163
|
+
# Helpers
|
|
164
|
+
#
|
|
164
165
|
def warning_size(id, name, keep)
|
|
165
166
|
message = "For droplet with id: #{id} and name: #{name} the maximum number #{keep} of snapshots is reached."
|
|
166
167
|
Log.warning message
|
data/lib/do_snapshot/mail.rb
CHANGED
data/lib/do_snapshot/version.rb
CHANGED
data/lib/do_snapshot.rb
CHANGED
|
@@ -65,7 +65,47 @@ describe DoSnapshot::Command do
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
describe
|
|
68
|
+
describe '.stop_droplet' do
|
|
69
|
+
it 'when raised with error' do
|
|
70
|
+
stub_droplet_stop_fail(droplet_id)
|
|
71
|
+
load_options
|
|
72
|
+
instance = cmd.api.droplet droplet_id
|
|
73
|
+
droplet = instance.droplet
|
|
74
|
+
expect { cmd.stop_droplet(droplet) }
|
|
75
|
+
.to raise_error(DoSnapshot::DropletShutdownError)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'when stopped' do
|
|
79
|
+
stub_droplet_stop(droplet_id)
|
|
80
|
+
load_options
|
|
81
|
+
instance = cmd.api.droplet droplet_id
|
|
82
|
+
droplet = instance.droplet
|
|
83
|
+
expect { cmd.stop_droplet(droplet) }
|
|
84
|
+
.not_to raise_error
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '.create_snapshot' do
|
|
89
|
+
it 'when raised with error' do
|
|
90
|
+
stub_droplet_snapshot_fail(droplet_id, snapshot_name)
|
|
91
|
+
load_options
|
|
92
|
+
instance = cmd.api.droplet droplet_id
|
|
93
|
+
droplet = instance.droplet
|
|
94
|
+
expect { cmd.create_snapshot(droplet) }
|
|
95
|
+
.to raise_error(DoSnapshot::SnapshotCreateError)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'when snapshot is created' do
|
|
99
|
+
stub_droplet_snapshot(droplet_id, snapshot_name)
|
|
100
|
+
load_options
|
|
101
|
+
instance = cmd.api.droplet droplet_id
|
|
102
|
+
droplet = instance.droplet
|
|
103
|
+
expect { cmd.create_snapshot(droplet) }
|
|
104
|
+
.not_to raise_error
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe '.fail_power_off' do
|
|
69
109
|
it 'when success' do
|
|
70
110
|
stub_droplet_inactive(droplet_id)
|
|
71
111
|
|
|
@@ -83,7 +123,7 @@ describe DoSnapshot::Command do
|
|
|
83
123
|
expect(log.buffer)
|
|
84
124
|
.to include 'Droplet id: 100823 is Failed to Power Off.'
|
|
85
125
|
expect(log.buffer)
|
|
86
|
-
|
|
126
|
+
.to include 'Droplet Not Found'
|
|
87
127
|
end
|
|
88
128
|
|
|
89
129
|
it 'with start error' do
|
|
@@ -105,9 +145,14 @@ describe DoSnapshot::Command do
|
|
|
105
145
|
log.quiet = true
|
|
106
146
|
end
|
|
107
147
|
|
|
108
|
-
def
|
|
148
|
+
def load_options(options = nil)
|
|
109
149
|
options ||= default_options
|
|
110
150
|
cmd.send('api=', nil)
|
|
111
|
-
cmd.
|
|
151
|
+
cmd.load_options(options, [:log, :mail, :smtp, :trace, :digital_ocean_client_id, :digital_ocean_api_key])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def snap_runner
|
|
155
|
+
load_options
|
|
156
|
+
cmd.snap
|
|
112
157
|
end
|
|
113
158
|
end
|
data/spec/do_snapshots_spec.rb
CHANGED
data/spec/shared/api_helpers.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
|
@@ -8,10 +8,9 @@ end
|
|
|
8
8
|
require 'do_snapshot/cli'
|
|
9
9
|
require 'webmock/rspec'
|
|
10
10
|
require 'digitalocean'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
require 'shared/uri_helpers'
|
|
11
|
+
require_relative 'shared/api_helpers'
|
|
12
|
+
require_relative 'shared/uri_helpers'
|
|
13
|
+
require_relative 'shared/environment'
|
|
15
14
|
require 'do_snapshot/core_ext/hash'
|
|
16
15
|
|
|
17
16
|
WebMock.disable_net_connect!(allow_localhost: true)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: do_snapshot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Merkulov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2015-01-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: digitalocean
|
|
@@ -263,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
263
263
|
version: '0'
|
|
264
264
|
requirements: []
|
|
265
265
|
rubyforge_project:
|
|
266
|
-
rubygems_version: 2.
|
|
266
|
+
rubygems_version: 2.4.5
|
|
267
267
|
signing_key:
|
|
268
268
|
specification_version: 4
|
|
269
269
|
summary: A command-line snapshot maker for your DigitalOcean droplets. Fully Automated.
|