do_snapshot 0.0.14 → 0.0.15

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -7
  3. data/lib/do_snapshot/adapter.rb +21 -0
  4. data/lib/do_snapshot/adapter/abstract.rb +44 -0
  5. data/lib/do_snapshot/adapter/digitalocean.rb +133 -0
  6. data/lib/do_snapshot/adapter/digitalocean_v2.rb +145 -0
  7. data/lib/do_snapshot/cli.rb +22 -12
  8. data/lib/do_snapshot/command.rb +7 -8
  9. data/lib/do_snapshot/version.rb +1 -1
  10. data/spec/do_snapshot/adapter/abstract_spec.rb +29 -0
  11. data/spec/do_snapshot/{api_spec.rb → adapter/digitalocean_spec.rb} +4 -3
  12. data/spec/do_snapshot/adapter/digitalocean_v2_spec.rb +223 -0
  13. data/spec/do_snapshot/cli_spec.rb +2 -1
  14. data/spec/do_snapshot/command_spec.rb +5 -8
  15. data/spec/fixtures/{error_message.json → digitalocean/v1/error_message.json} +0 -0
  16. data/spec/fixtures/{response_event.json → digitalocean/v1/response_event.json} +0 -0
  17. data/spec/fixtures/{show_droplet.json → digitalocean/v1/show_droplet.json} +0 -0
  18. data/spec/fixtures/{show_droplet_inactive.json → digitalocean/v1/show_droplet_inactive.json} +0 -0
  19. data/spec/fixtures/{show_droplets.json → digitalocean/v1/show_droplets.json} +0 -0
  20. data/spec/fixtures/{show_droplets_empty.json → digitalocean/v1/show_droplets_empty.json} +0 -0
  21. data/spec/fixtures/{show_event_done.json → digitalocean/v1/show_event_done.json} +0 -0
  22. data/spec/fixtures/{show_event_start.json → digitalocean/v1/show_event_start.json} +0 -0
  23. data/spec/fixtures/digitalocean/v2/empty.json +0 -0
  24. data/spec/fixtures/digitalocean/v2/error_message.json +4 -0
  25. data/spec/fixtures/digitalocean/v2/response_event.json +29 -0
  26. data/spec/fixtures/digitalocean/v2/show_droplet.json +102 -0
  27. data/spec/fixtures/digitalocean/v2/show_droplet_inactive.json +102 -0
  28. data/spec/fixtures/digitalocean/v2/show_droplets.json +284 -0
  29. data/spec/fixtures/digitalocean/v2/show_droplets_empty.json +5 -0
  30. data/spec/fixtures/digitalocean/v2/show_event_done.json +29 -0
  31. data/spec/fixtures/digitalocean/v2/show_event_power_off_done.json +29 -0
  32. data/spec/fixtures/digitalocean/v2/show_event_power_off_start.json +29 -0
  33. data/spec/fixtures/digitalocean/v2/show_event_power_on_done.json +29 -0
  34. data/spec/fixtures/digitalocean/v2/show_event_power_on_start.json +29 -0
  35. data/spec/fixtures/digitalocean/v2/show_event_start.json +29 -0
  36. data/spec/shared/api_helpers.rb +5 -80
  37. data/spec/shared/api_v1_helpers.rb +97 -0
  38. data/spec/shared/api_v2_helpers.rb +152 -0
  39. data/spec/shared/environment.rb +5 -14
  40. data/spec/shared/uri_helpers.rb +1 -0
  41. data/spec/spec_helper.rb +5 -3
  42. metadata +74 -23
  43. data/lib/do_snapshot/api.rb +0 -144
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d5378c837dccf17028be844060295b82be0349b8
4
- data.tar.gz: 4c548fa5590fb87b696c7dba43a6fa5330e2dc32
3
+ metadata.gz: daa05c7961b1c4dbf9275fb6242817289a52f9c8
4
+ data.tar.gz: fc001ff0e7c71e660220972f9494106d1293e162
5
5
  SHA512:
6
- metadata.gz: f97a8affb32bd8e371df9ec8646cc46ac6ef66f624bc9939bc156a6ce703b7055096ab0b4cfc15c1c162e25a55abf333578ce977bb0afdaee6ca662dfa11b643
7
- data.tar.gz: b67bd75db1fe228f4e1cbd624a5ba4b2fa0f3e0c8e827e1cdcac2f8af38dab941bfb68f970a8ff2b9faaf097acb5fef9c62e33ee3d65c97a60217d6b54001bf7
6
+ metadata.gz: a1fd3c4e7b308883ffd689aadfaed8cc45295879ad471636ce0735e63f3e426387caca44ed110f5de6fc3190871f953c11cafd27121e45038c4bf7aa51bd1dc1
7
+ data.tar.gz: 754e0414a459cc7125ca7991068cde13f3e12cfe82adbd8c07ad62f9d07ab6cd662c1a5a2a794b1320993d3fee816813163548304e5dd268767e2625a606cf52
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # DoSnapshot CLI
2
+
3
+ [![Join the chat at https://gitter.im/merqlove/do_snapshot](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/merqlove/do_snapshot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2
4
  [Project Page at Digital Ocean](https://www.digitalocean.com/community/projects/dosnapshot), comment or vote for this project.
3
5
 
4
6
  [![Gem Version](https://badge.fury.io/rb/do_snapshot.svg)](http://badge.fury.io/rb/do_snapshot)
@@ -26,7 +28,9 @@ Here some features:
26
28
 
27
29
  ## Compatibility
28
30
 
29
- Ruby versions: 1.9.3 and higher. JRuby in 1.9 mode is also supported.
31
+ Ruby versions 2.0.0 and higher. JRuby 9.0.0.0 or later is also supported.
32
+
33
+ *Ruby versions 1.9.3 or less and JRuby in 1.9-mode supported in releases 0.0.14 or earlier.*
30
34
 
31
35
  <img src="https://raw.githubusercontent.com/merqlove/do_snapshot/master/assets/example.png" style="max-width:100%" alt="DoSnaphot example">
32
36
 
@@ -73,23 +77,37 @@ Mainly it's pretty simple:
73
77
 
74
78
  $ do_snapshot --only 123456 -k 5 -c -v
75
79
 
76
- ### Setup
80
+ ### Setup
77
81
 
78
- How to set DigitalOcean API keys:
82
+ ### Digitalocean API V1:
83
+ You'll need to generate an access token in Digital Ocean's control panel at https://cloud.digitalocean.com/api_access
79
84
 
80
85
  $ export DIGITAL_OCEAN_CLIENT_ID="SOMEID"
81
86
  $ export DIGITAL_OCEAN_API_KEY="SOMEKEY"
82
-
87
+
83
88
  If you want to set keys without environment, than set it via options when you run do_snapshot:
84
89
 
85
90
  $ do_snapshot --digital-ocean-client-id YOURLONGAPICLIENTID --digital-ocean-api-key YOURLONGAPIKEY
91
+
92
+ ### Digitalocean API V2:
93
+ You'll need to generate an access token in Digital Ocean's control panel at https://cloud.digitalocean.com/settings/applications
94
+
95
+ $ export DIGITAL_OCEAN_ACCESS_TOKEN="SOMETOKEN"
96
+
97
+ If you want to set keys without environment, than set it via options when you run do_snapshot:
98
+
99
+ $ do_snapshot --digital-ocean-access-token YOURLONGTOKEN
86
100
 
87
101
  ### How-To (Here is also [Longren Tutorial](https://longren.io/automate-making-snapshots-of-your-digitalocean-droplets/))
88
102
 
89
103
  Here we `keeping` only 5 **latest** snapshots and cleanup older after new one is created. If creation of snapshots failed no one will be deleted. By default we keeping `10` droplets.
90
104
 
91
105
  $ do_snapshot --keep 5 -c
106
+
107
+ Using API V2:
92
108
 
109
+ $ do_snapshot -p 2
110
+
93
111
  Keep latest 3 from selected droplet:
94
112
 
95
113
  $ do_snapshot --only 123456 --keep 3
@@ -141,6 +159,8 @@ For working mailer you need to set e-mail settings via run options.
141
159
  aliases: s, snap, create
142
160
 
143
161
  Options:
162
+ -p, [--protocol=1] # Select api version.
163
+ # Default: 1
144
164
  -o, [--only=123456 123456 123456] # Select some droplets.
145
165
  -e, [--exclude=123456 123456 123456] # Except some droplets.
146
166
  -k, [--keep=5] # How much snapshots you want to keep?
@@ -148,7 +168,7 @@ For working mailer you need to set e-mail settings via run options.
148
168
  -d, [--delay=5] # Delay between snapshot operation status requests.
149
169
  # Default: 10
150
170
  [--timeout=250] # Timeout in sec's for events like Power Off or Create Snapshot.
151
- # Default: 600
171
+ # Default: 3600
152
172
  -m, [--mail=to:yourmail@example.com] # Receive mail if fail or maximum is reached.
153
173
  -t, [--smtp=user_name:yourmail@example.com password:password] # SMTP options.
154
174
  -l, [--log=/Users/someone/.do_snapshot/main.log] # Log file path. By default logging is disabled.
@@ -156,6 +176,7 @@ For working mailer you need to set e-mail settings via run options.
156
176
  -s, [--stop], [--no-stop] # Stop creating snapshots if maximum is reached.
157
177
  -v, [--trace], [--no-trace] # Verbose mode.
158
178
  -q, [--quiet], [--no-quiet] # Quiet mode. If don't need any messages in console.
179
+ [--digital-ocean-access-token=YOURLONGAPITOKEN] # DIGITAL_OCEAN_ACCESS_TOKEN. if you can't use environment.
159
180
  [--digital-ocean-client-id=YOURLONGAPICLIENTID] # DIGITAL_OCEAN_CLIENT_ID. if you can't use environment.
160
181
  [--digital-ocean-api-key=YOURLONGAPIKEY] # DIGITAL_OCEAN_API_KEY. if you can't use environment.
161
182
 
@@ -167,7 +188,8 @@ For working mailer you need to set e-mail settings via run options.
167
188
  ## Dependencies:
168
189
 
169
190
  - [Thor](https://github.com/erikhuda/thor) for CLI.
170
- - [Digitalocean](https://github.com/scottmotte/digitalocean) for API requests.
191
+ - [Digitalocean](https://github.com/scottmotte/digitalocean) for API V1 requests.
192
+ - [DropletKit](https://github.com/digitalocean/droplet_kit) for API V2 requests.
171
193
  - [Pony](https://github.com/benprew/pony) for mail notifications.
172
194
 
173
195
  ## Contributing
@@ -182,6 +204,6 @@ For working mailer you need to set e-mail settings via run options.
182
204
 
183
205
  $ rake spec
184
206
 
185
- Copyright (c) 2014 Alexander Merkulov
207
+ Copyright (c) 2015 Alexander Merkulov
186
208
 
187
209
  MIT License
@@ -0,0 +1,21 @@
1
+ require_relative 'adapter/abstract'
2
+
3
+ require_relative 'adapter/digitalocean'
4
+ require_relative 'adapter/digitalocean_v2'
5
+
6
+ module DoSnapshot
7
+ # Adapter interface for API connections
8
+ # Ability to select DigitalOcean API versions.
9
+ #
10
+ module Adapter
11
+ def api(protocol, options)
12
+ case protocol
13
+ when 2
14
+ return DigitaloceanV2.new(options)
15
+ else
16
+ return Digitalocean.new(options)
17
+ end
18
+ end
19
+ module_function :api
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module DoSnapshot
4
+ module Adapter
5
+ # API for CLI commands
6
+ # Operating with Digital Ocean.
7
+ #
8
+ class Abstract
9
+ attr_accessor :delay
10
+ attr_accessor :timeout
11
+
12
+ def initialize(options)
13
+ check_keys
14
+ set_id
15
+ options.each_pair do |key, option|
16
+ send("#{key}=", option)
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def set_id; end
23
+
24
+ def check_keys; end
25
+
26
+ # Waiting for event exit
27
+ def wait_event(id)
28
+ Log.debug "Event Id: #{id}"
29
+ time = Time.now
30
+ sleep(delay) until get_event_status(id, time)
31
+ end
32
+
33
+ def after_cleanup(droplet_id, droplet_name, snapshot, event)
34
+ if !event
35
+ Log.error "Destroy of snapshot #{snapshot.name} for droplet id: #{droplet_id} name: #{droplet_name} is failed."
36
+ elsif event && !event.status.include?('OK')
37
+ Log.error event.message
38
+ else
39
+ Log.debug "Snapshot name: #{snapshot.name} delete requested."
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,133 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'digitalocean_c' unless defined?(::DigitaloceanC)
3
+
4
+ module DoSnapshot
5
+ module Adapter
6
+ # API for CLI commands
7
+ # Operating with Digital Ocean.
8
+ #
9
+ class Digitalocean < Abstract
10
+ # Get single droplet from DigitalOcean
11
+ #
12
+ def droplet(id)
13
+ # noinspection RubyResolve
14
+ response = ::DigitaloceanC::Droplet.find(id)
15
+ fail DropletFindError, response.message unless response.status.include? 'OK'
16
+ response.droplet
17
+ end
18
+
19
+ # Get droplets list from DigitalOcean
20
+ #
21
+ def droplets
22
+ # noinspection RubyResolve
23
+ response = ::DigitaloceanC::Droplet.all
24
+ fail DropletListError, response.message unless response.status.include? 'OK'
25
+ response.droplets
26
+ end
27
+
28
+ def snapshots(instance)
29
+ instance.snapshots
30
+ end
31
+
32
+ # Power On request for Droplet
33
+ #
34
+ def start_droplet(id)
35
+ # noinspection RubyResolve
36
+ instance = droplet(id)
37
+
38
+ if instance.status.include? 'active'
39
+ Log.error 'Droplet is still running.'
40
+ else
41
+ power_on id
42
+ end
43
+ end
44
+
45
+ # Power Off request for Droplet
46
+ #
47
+ def stop_droplet(id)
48
+ # noinspection RubyResolve,RubyResolve
49
+ event = ::DigitaloceanC::Droplet.power_off(id)
50
+
51
+ fail event.message unless event.status.include? 'OK'
52
+
53
+ # noinspection RubyResolve
54
+ wait_event(event.event_id)
55
+ rescue => e
56
+ raise DropletShutdownError.new(id), e.message, e.backtrace
57
+ end
58
+
59
+ # Sending event to create snapshot via DigitalOcean API and wait for success
60
+ #
61
+ def create_snapshot(id, name)
62
+ # noinspection RubyResolve,RubyResolve
63
+ event = ::DigitaloceanC::Droplet.snapshot(id, name: name)
64
+
65
+ if !event
66
+ fail 'Something wrong with DigitalOcean or with your connection :)'
67
+ elsif event && !event.status.include?('OK')
68
+ fail event.message
69
+ end
70
+
71
+ # noinspection RubyResolve
72
+ wait_event(event.event_id)
73
+ end
74
+
75
+ # Cleanup our snapshots.
76
+ #
77
+ def cleanup_snapshots(instance, size) # rubocop:disable MethodLength
78
+ (0..size).each do |i|
79
+ # noinspection RubyResolve
80
+ snapshot = instance.snapshots[i]
81
+ event = ::DigitaloceanC::Image.destroy(snapshot.id)
82
+
83
+ after_cleanup(instance.id, instance.name, snapshot, event)
84
+ end
85
+ end
86
+
87
+ def check_keys
88
+ Log.debug 'Checking DigitalOcean Id\'s.'
89
+ %w( DIGITAL_OCEAN_CLIENT_ID DIGITAL_OCEAN_API_KEY ).each do |key|
90
+ Log.error "You must have #{key} in environment or set it via options." if ENV[key].blank?
91
+ end
92
+ end
93
+
94
+ protected
95
+
96
+ # Set id's of Digital Ocean API.
97
+ #
98
+ def set_id
99
+ Log.debug 'Setting DigitalOcean Id\'s.'
100
+ ::DigitaloceanC.client_id = ENV['DIGITAL_OCEAN_CLIENT_ID']
101
+ ::DigitaloceanC.api_key = ENV['DIGITAL_OCEAN_API_KEY']
102
+ end
103
+
104
+ # Looking for event status.
105
+ # Before snapshot we to know that machine has powered off.
106
+ #
107
+ def get_event_status(id, time)
108
+ if (Time.now - time) > @timeout
109
+ Log.debug "Event #{id} finished by timeout #{time}"
110
+ return true
111
+ end
112
+
113
+ event = ::DigitaloceanC::Event.find(id)
114
+ fail event.message unless event.status.include?('OK')
115
+ # noinspection RubyResolve,RubyResolve
116
+ event.event.percentage && event.event.percentage.include?('100') ? true : false
117
+ end
118
+
119
+ # Request Power On for droplet
120
+ #
121
+ def power_on(id)
122
+ # noinspection RubyResolve
123
+ event = ::DigitaloceanC::Droplet.power_on(id)
124
+ case event && event.status
125
+ when 'OK'
126
+ Log.info 'Power On has been requested.'
127
+ else
128
+ Log.error 'Power On failed to request.'
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,145 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'droplet_kit' unless defined?(::DropletKit)
3
+
4
+ module DoSnapshot
5
+ module Adapter
6
+ # API for CLI commands
7
+ # Operating with Digital Ocean.
8
+ #
9
+ class DigitaloceanV2 < Abstract
10
+ attr_reader :client
11
+
12
+ # Get single droplet from DigitalOcean
13
+ #
14
+ def droplet(id)
15
+ # noinspection RubyResolve
16
+ response = client.droplets.find(id: id)
17
+ fail DropletFindError unless response
18
+ response
19
+ rescue => e
20
+ raise DropletFindError, e.message
21
+ end
22
+
23
+ # Get droplets list from DigitalOcean
24
+ #
25
+ def droplets
26
+ # noinspection RubyResolve
27
+ response = client.droplets.all
28
+ fail DropletListError unless response
29
+ response.each
30
+ rescue => e
31
+ raise DropletListError, e.message
32
+ end
33
+
34
+ def snapshots(instance)
35
+ instance.snapshot_ids
36
+ end
37
+
38
+ # Power On request for Droplet
39
+ #
40
+ def start_droplet(id)
41
+ # noinspection RubyResolve
42
+ instance = droplet(id)
43
+
44
+ if instance.status && instance.status.include?('active')
45
+ Log.error 'Droplet is still running.'
46
+ else
47
+ power_on id
48
+ end
49
+ end
50
+
51
+ # Power Off request for Droplet
52
+ #
53
+ def stop_droplet(id)
54
+ # noinspection RubyResolve,RubyResolve
55
+ event = client.droplet_actions.power_off(droplet_id: id)
56
+
57
+ # noinspection RubyResolve
58
+ wait_event(event.id)
59
+ rescue => e
60
+ raise DropletShutdownError.new(id), e.message, e.backtrace
61
+ end
62
+
63
+ # Sending event to create snapshot via DigitalOcean API and wait for success
64
+ #
65
+ def create_snapshot(id, name)
66
+ # noinspection RubyResolve,RubyResolve
67
+ event = client.droplet_actions.snapshot(droplet_id: id, name: name)
68
+
69
+ # noinspection RubyResolve
70
+ wait_event(event.id)
71
+ rescue => e
72
+ raise e.message, e.backtrace
73
+ end
74
+
75
+ # Cleanup our snapshots.
76
+ #
77
+ def cleanup_snapshots(instance, size) # rubocop:disable MethodLength
78
+ (0..size).each do |i|
79
+ # noinspection RubyResolve
80
+ snapshot = instance.snapshot_ids[i]
81
+ event = client.images.delete(id: snapshot)
82
+
83
+ unless event.is_a?(TrueClass)
84
+ Log.debug event
85
+ event = false
86
+ end
87
+
88
+ after_cleanup(instance.id, instance.name, snapshot, event)
89
+ end
90
+ end
91
+
92
+ def check_keys
93
+ Log.debug 'Checking DigitalOcean Access Token.'
94
+ %w( DIGITAL_OCEAN_ACCESS_TOKEN ).each do |key|
95
+ Log.error "You must have #{key} in environment or set it via options." if ENV[key].blank?
96
+ end
97
+ end
98
+
99
+ # Set id's of Digital Ocean API.
100
+ #
101
+ def set_id
102
+ Log.debug 'Setting DigitalOcean Access Token.'
103
+ @client = ::DropletKit::Client.new(access_token: ENV['DIGITAL_OCEAN_ACCESS_TOKEN'])
104
+ end
105
+
106
+ protected
107
+
108
+ def after_cleanup(droplet_id, droplet_name, snapshot, event)
109
+ if !event
110
+ Log.error "Destroy of snapshot #{snapshot} for droplet id: #{droplet_id} name: #{droplet_name} is failed."
111
+ else
112
+ Log.debug "Snapshot: #{snapshot} delete requested."
113
+ end
114
+ end
115
+
116
+ # Looking for event status.
117
+ # Before snapshot we to know that machine has powered off.
118
+ #
119
+ def get_event_status(id, time)
120
+ if (Time.now - time) > @timeout
121
+ Log.debug "Event #{id} finished by timeout #{time}"
122
+ return true
123
+ end
124
+
125
+ action = client.actions.find(id: id)
126
+ # noinspection RubyResolve,RubyResolve
127
+ action.status.include?('completed') ? true : false
128
+ rescue => e
129
+ raise e.message, e.backtrace
130
+ end
131
+
132
+ # Request Power On for droplet
133
+ #
134
+ def power_on(id)
135
+ # noinspection RubyResolve
136
+ event = client.droplet_actions.power_on(droplet_id: id)
137
+ if event.status.include?('in-progress')
138
+ Log.info 'Power On has been requested.'
139
+ else
140
+ Log.error 'Power On failed to request.'
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end