do_snapshot 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
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