do_snapshot 0.0.12 → 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/do_snapshot.svg)](http://badge.fury.io/rb/do_snapshot)
|
4
5
|
[![Build Status](https://travis-ci.org/merqlove/do_snapshot.svg?branch=master)](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.
|