tugboat 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbcf98dca8237e5bfd743b99bff3f3a328adef69
4
- data.tar.gz: 8a9d169c00582cc9c71de34f38573d35cfe1fee5
3
+ metadata.gz: 4a56c6cb7473968939a5e1a56012640156bc284a
4
+ data.tar.gz: 6283168b5d267c420c74e57c416ac3be8839b8d4
5
5
  SHA512:
6
- metadata.gz: 8cce0f1c41b15927f149aa687b5f9e2f3edf05e50da812f54cac4ba69253f64a92bc792f9a77d79e8a09103e4c804f20172c121bc1da5337465b82ff1304a9aa
7
- data.tar.gz: e0dc3a1f73e12a0da367269228fbcb79edcd58c995f1186e642f7be8283cf0a11a0924308e3ef05337deb471a131942ec5c1d30bcedf23bd91df3344891398c7
6
+ metadata.gz: 507273ace57c2dc9f5a9f948d1da9cdd5cb06adb3476ad945437fcf8311c0bbcf17deed1d7b7fe3141f520163c329cfeaa63174631ba58250dcb05136ecd97bc
7
+ data.tar.gz: cb40271dba29c11d8e3bcec36de00450bb4bf866edfb79b9ca0b0a1809ff52949737e4e4e37b1688a95ad2c22f427b16364f2745a6c81761d71752ca042751ee
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [v4.1.0](https://github.com/petems/tugboat/tree/v4.1.0) (2018-03-11)
6
+ [Full Changelog](https://github.com/petems/tugboat/compare/v4.0.0...v4.1.0)
7
+
8
+ **Implemented enhancements:**
9
+
10
+ - Commands like "droplets" need formatting switches [\#206](https://github.com/petems/tugboat/issues/206)
11
+ - Add a scp command [\#120](https://github.com/petems/tugboat/issues/120)
12
+ - Turn Backups on/off [\#51](https://github.com/petems/tugboat/issues/51)
13
+
14
+ **Merged pull requests:**
15
+
16
+ - Update README typo. [\#298](https://github.com/petems/tugboat/pull/298) ([simi](https://github.com/simi))
17
+ - Add ability to list snapshots [\#296](https://github.com/petems/tugboat/pull/296) ([petems](https://github.com/petems))
18
+ - Add backup\_config setting [\#295](https://github.com/petems/tugboat/pull/295) ([petems](https://github.com/petems))
19
+ - Adds porcelain option to droplets command [\#294](https://github.com/petems/tugboat/pull/294) ([petems](https://github.com/petems))
20
+ - Adds new SCP feature [\#291](https://github.com/petems/tugboat/pull/291) ([petems](https://github.com/petems))
21
+
5
22
  ## [v4.0.0](https://github.com/petems/tugboat/tree/v4.0.0) (2017-12-03)
6
23
  [Full Changelog](https://github.com/petems/tugboat/compare/v3.1.0...v4.0.0)
7
24
 
@@ -13,6 +30,7 @@ All notable changes to this project will be documented in this file.
13
30
 
14
31
  **Merged pull requests:**
15
32
 
33
+ - Bump Version to 4.0.0 [\#293](https://github.com/petems/tugboat/pull/293) ([petems](https://github.com/petems))
16
34
  - Removing `Assigned but unused variable` warnings [\#292](https://github.com/petems/tugboat/pull/292) ([petems](https://github.com/petems))
17
35
  - Adds helper for showing snapshot parameter [\#290](https://github.com/petems/tugboat/pull/290) ([petems](https://github.com/petems))
18
36
  - Updates Faraday middleware gem [\#289](https://github.com/petems/tugboat/pull/289) ([petems](https://github.com/petems))
data/README.md CHANGED
@@ -6,7 +6,7 @@ A command line tool for interacting with your [DigitalOcean](https://www.digital
6
6
 
7
7
  ## History
8
8
 
9
- When Tugboat was created, DigitalOcean was an extremely new cloud provider. They'd only released their public beta back in [2012](https://whoapi.com/blog/1497/fast-growing-digitalocean-is-fueled-by-customer-love/), and their new SSD backed machines only primiered in early [2013](https://techcrunch.com/2013/01/15/techstars-graduate-digitalocean-switches-to-ssd-for-its-5-per-month-vps-to-take-on-linode-and-rackspace/).
9
+ When Tugboat was created, DigitalOcean was an extremely new cloud provider. They'd only released their public beta back in [2012](https://whoapi.com/blog/1497/fast-growing-digitalocean-is-fueled-by-customer-love/), and their new SSD backed machines only premiered in early [2013](https://techcrunch.com/2013/01/15/techstars-graduate-digitalocean-switches-to-ssd-for-its-5-per-month-vps-to-take-on-linode-and-rackspace/).
10
10
 
11
11
  Tugboat started out life around that time, [back in April 2013](https://github.com/pearkes/tugboat/commit/f0fbc1f438cce81c286f0e60014dc4393ac95cb6). Back then, there were no official libraries for DigitalOcean, and the 1.0 API was a bit unstable and occasionally flakey.
12
12
 
@@ -81,6 +81,44 @@ defaults:
81
81
  pearkes-admin-001 (ip: 30.30.30.3, status: active, region: nyc2, id: 13231512)
82
82
  pearkes-api-001 (ip: 30.30.30.5, status: active, region: nyc2, id: 13231513)
83
83
 
84
+ If you wish to use the droplet listing as part of scripting or munging output, you can use the `--porcelain`:
85
+
86
+ $ tugboat droplets --attribute=ip4
87
+ pearkes-web-001,30.30.30.1
88
+ pearkes-admin-001,30.30.30.3
89
+ pearkes-api-001,30.30.30.5
90
+
91
+ Or `--attribute` parameter:
92
+
93
+ $ tugboat droplets --porcelain
94
+ name pearkes-web-001
95
+ id 13231515
96
+ status active
97
+ ip4 330.30.30.1
98
+ region lon1
99
+ image 6918990
100
+ size 1gb
101
+ backups_active false
102
+
103
+ name pearkes-admin-001
104
+ id 13231513
105
+ status active
106
+ ip4 30.30.30.3
107
+ region lon1
108
+ image 6918990
109
+ size 1gb
110
+ backups_active false
111
+
112
+ name pearkes-web-001
113
+ id 13231514
114
+ status active
115
+ ip4 30.30.30.5
116
+ region lon1
117
+ image 6918990
118
+ size 1gb
119
+ backups_active true
120
+
121
+
84
122
  ### Fuzzy name matching
85
123
 
86
124
  You can pass a unique fragment of a droplets name for interactions
@@ -116,6 +154,21 @@ match.
116
154
  Welcome to Ubuntu 12.10 (GNU/Linux 3.5.0-17-generic x86_64)
117
155
  pearkes@pearkes-admin-001:~#
118
156
 
157
+ ### SCP files to droplet
158
+
159
+ *You can configure an SSH username and key path in `tugboat authorize`,
160
+ or by changing your `~/.tugboat`.*
161
+
162
+ This lets you scp a file into a droplet by providing it's name, or a partial
163
+ match.
164
+
165
+ $ tugboat scp test-scp /tmp/foo /tmp/bar
166
+ Droplet fuzzy name provided. Finding droplet ID...done, 72025053 (test-scp)
167
+ Executing SCP on Droplet (test-scp)...
168
+ Attempting SCP with `scp -i /Users/petems/.ssh/digital_ocean /tmp/foo root@132.61.164.113:/tmp/bar`
169
+ foo
170
+ 100% 0 0.0KB/s 00:00
171
+
119
172
  ### Create a droplet
120
173
 
121
174
  $ tugboat create pearkes-www-002 -s 512mb -i ubuntu-12-04-x64 -r nyc2 -k 11251
@@ -183,6 +236,18 @@ Print a single attribute.
183
236
  $ tugboat resize admin -s 66
184
237
  Queuing resize for 13231512 (pearkes-admin-001)...done
185
238
 
239
+ ### Enabling backups on a droplet
240
+
241
+ $ tugboat backup_config admin --on
242
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
243
+ Backup action enable backups is complete
244
+
245
+ ### Disabling backups on a droplet
246
+
247
+ $ tugboat backup_config admin --off
248
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
249
+ Backup action disable backups is complete
250
+
186
251
  ### List Available Images
187
252
 
188
253
  You can list all images
@@ -206,6 +271,18 @@ Or just list images that you have created.
206
271
  My application image (id: 6376601, distro: Ubuntu)
207
272
  ....
208
273
 
274
+ ### List Current Snapshots
275
+
276
+ $ tugboat snapshots
277
+ code-freeze-backup-october (id: 2013184, resource_type: droplet, created_at: 2016-10-06T11:43:06Z)
278
+ test-admin 2017-05-31 (id: 20234485, resource_type: droplet, created_at: 2017-05-31T02:07:07Z)
279
+ test-admin 2017-11-08 (id: 21133567, resource_type: droplet, created_at: 2017-11-08T02:49:09Z)
280
+ test-admin 2017-11-15 (id: 22355454, resource_type: droplet, created_at: 2017-11-15T03:11:08Z)
281
+ test-admin 2017-11-22 (id: 24523423, resource_type: droplet, created_at: 2017-11-22T03:10:09Z)
282
+ test-admin 2017-11-29 (id: 26212345, resource_type: droplet, created_at: 2017-11-29T03:15:25Z)
283
+ ....
284
+
285
+
209
286
  ### List Available Sizes
210
287
 
211
288
  $ tugboat sizes
@@ -37,6 +37,33 @@ module Tugboat
37
37
  'user_quiet' => options[:quiet])
38
38
  end
39
39
 
40
+ desc 'backup_config FUZZY_NAME', 'Manage backups on a droplet.'
41
+ method_option 'id',
42
+ type: :string,
43
+ aliases: '-i',
44
+ desc: 'The ID of the droplet.'
45
+ method_option 'name',
46
+ type: :string,
47
+ aliases: '-n',
48
+ desc: 'The exact name of the droplet'
49
+ method_option 'enable',
50
+ type: :boolean,
51
+ aliases: '--on',
52
+ desc: 'Enable backups on the droplet.'
53
+ method_option 'disable',
54
+ type: :boolean,
55
+ aliases: '--off',
56
+ desc: 'Disable backups on the droplet.'
57
+ def backup_config(name = nil, status = nil)
58
+ Middleware.sequence_backup_config.call('tugboat_action' => __method__,
59
+ 'user_droplet_id' => options[:id],
60
+ 'user_droplet_name' => options[:name],
61
+ 'user_droplet_fuzzy_name' => name,
62
+ 'enable' => options[:enable],
63
+ 'disable' => options[:disable],
64
+ 'user_quiet' => options[:quiet])
65
+ end
66
+
40
67
  desc 'config', 'Show your current config information'
41
68
  long_desc "This shows the current information in the .tugboat config
42
69
  being used by the app
@@ -71,12 +98,26 @@ module Tugboat
71
98
  default: 20,
72
99
  aliases: '-p',
73
100
  desc: 'Chose how many results to fetch from the DigitalOcean API (larger is slower)'
101
+ method_option 'attribute',
102
+ type: :string,
103
+ aliases: '-a',
104
+ desc: 'The name of the attribute to print.'
105
+ method_option 'porcelain',
106
+ type: :boolean,
107
+ desc: 'Give the output in an easy-to-parse format for scripts.'
108
+ method_option 'include_name',
109
+ type: :boolean,
110
+ default: true,
111
+ desc: 'Include the name when listing attributes from droplets.'
74
112
  desc 'droplets [OPTIONS]', 'Retrieve a list of your droplets'
75
113
  def droplets
76
114
  Middleware.sequence_list_droplets.call('tugboat_action' => __method__,
77
115
  'user_quiet' => options[:quiet],
78
116
  'include_urls' => options['include_urls'],
79
- 'per_page' => options['per_page'],)
117
+ 'per_page' => options['per_page'],
118
+ 'attribute' => options[:attribute],
119
+ 'porcelain' => options[:porcelain],
120
+ 'include_name' => options[:include_name])
80
121
  end
81
122
 
82
123
  desc 'images [OPTIONS]', 'Retrieve a list of images'
@@ -139,6 +180,48 @@ module Tugboat
139
180
  'user_quiet' => options[:quiet])
140
181
  end
141
182
 
183
+ desc 'scp FUZZY_NAME FROM_PATH TO_PATH', 'scp files to a droplet'
184
+ method_option 'id',
185
+ type: :string,
186
+ aliases: '-i',
187
+ desc: 'The ID of the droplet.'
188
+ method_option 'from_path',
189
+ type: :string,
190
+ aliases: '-from',
191
+ desc: 'The path of the local file'
192
+ method_option 'to_path',
193
+ type: :string,
194
+ aliases: '-to',
195
+ desc: 'The path to copy to on the remote droplet'
196
+ method_option 'name',
197
+ type: :string,
198
+ aliases: '-n',
199
+ desc: 'The exact name of the droplet'
200
+ method_option 'ssh_user',
201
+ type: :string,
202
+ aliases: '-u',
203
+ desc: 'Specifies which user to log in as'
204
+ method_option 'scp_command',
205
+ type: :string,
206
+ aliases: ['-c'],
207
+ desc: 'Command to run to copy the file, eg scp, rsync (defaults to scp)'
208
+ method_option 'wait',
209
+ type: :boolean,
210
+ aliases: '-w',
211
+ desc: 'Wait for droplet to become active before trying to SSH'
212
+ def scp(name = nil, from_path, to_path)
213
+ Middleware.sequence_scp_droplet.call('tugboat_action' => __method__,
214
+ 'user_droplet_id' => options[:id],
215
+ 'user_droplet_name' => options[:name],
216
+ 'user_droplet_fuzzy_name' => name,
217
+ 'user_from_file' => from_path,
218
+ 'user_to_file' => to_path,
219
+ 'user_droplet_ssh_user' => options[:ssh_user],
220
+ 'user_scp_command' => options[:scp_command],
221
+ 'user_droplet_ssh_wait' => options[:wait],
222
+ 'user_quiet' => options[:quiet])
223
+ end
224
+
142
225
  desc 'create NAME', 'Create a droplet.'
143
226
  method_option 'size',
144
227
  type: :string,
@@ -456,6 +539,20 @@ module Tugboat
456
539
  'user_quiet' => options[:quiet])
457
540
  end
458
541
 
542
+ method_option 'per_page',
543
+ type: :boolean,
544
+ default: 20,
545
+ aliases: '-p',
546
+ desc: 'Chose how many results to fetch from the DigitalOcean API (larger is slower)'
547
+ desc 'snapshots [OPTIONS]', 'Retrieve a list of your snapshots'
548
+ def snapshots
549
+ Middleware.sequence_list_snapshots.call(
550
+ 'tugboat_action' => __method__,
551
+ 'user_quiet' => options[:quiet],
552
+ 'per_page' => options['per_page'],
553
+ )
554
+ end
555
+
459
556
  desc 'password-reset FUZZY_NAME', 'Reset root password'
460
557
  method_option 'id',
461
558
  type: :numeric,
@@ -5,6 +5,7 @@ module Tugboat
5
5
  autoload :AddKey, 'tugboat/middleware/add_key'
6
6
  autoload :AskForCredentials, 'tugboat/middleware/ask_for_credentials'
7
7
  autoload :Base, 'tugboat/middleware/base'
8
+ autoload :BackupConfig, 'tugboat/middleware/backup_setting'
8
9
  autoload :CheckConfiguration, 'tugboat/middleware/check_configuration'
9
10
  autoload :CheckCredentials, 'tugboat/middleware/check_credentials'
10
11
  autoload :CheckDropletActive, 'tugboat/middleware/check_droplet_active'
@@ -27,12 +28,14 @@ module Tugboat
27
28
  autoload :ListImages, 'tugboat/middleware/list_images'
28
29
  autoload :ListRegions, 'tugboat/middleware/list_regions'
29
30
  autoload :ListSizes, 'tugboat/middleware/list_sizes'
31
+ autoload :ListSnapshots, 'tugboat/middleware/list_snapshots'
30
32
  autoload :ListSSHKeys, 'tugboat/middleware/list_ssh_keys'
31
33
  autoload :PasswordReset, 'tugboat/middleware/password_reset'
32
34
  autoload :ResizeDroplet, 'tugboat/middleware/resize_droplet'
33
35
  autoload :RestartDroplet, 'tugboat/middleware/restart_droplet'
34
36
  autoload :SnapshotDroplet, 'tugboat/middleware/snapshot_droplet'
35
37
  autoload :SSHDroplet, 'tugboat/middleware/ssh_droplet'
38
+ autoload :SCPDroplet, 'tugboat/middleware/scp_droplet'
36
39
  autoload :StartDroplet, 'tugboat/middleware/start_droplet'
37
40
  autoload :WaitForState, 'tugboat/middleware/wait_for_state'
38
41
 
@@ -49,6 +52,16 @@ module Tugboat
49
52
  end
50
53
  end
51
54
 
55
+ def self.sequence_backup_config
56
+ ::Middleware::Builder.new do
57
+ use InjectConfiguration
58
+ use CheckConfiguration
59
+ use InjectClient
60
+ use FindDroplet
61
+ use BackupConfig
62
+ end
63
+ end
64
+
52
65
  # This checks that the credentials in ~/.tugboat are valid
53
66
  def self.sequence_verify
54
67
  ::Middleware::Builder.new do
@@ -148,6 +161,18 @@ module Tugboat
148
161
  end
149
162
  end
150
163
 
164
+ # SSH into a droplet
165
+ def self.sequence_scp_droplet
166
+ ::Middleware::Builder.new do
167
+ use InjectConfiguration
168
+ use CheckConfiguration
169
+ use InjectClient
170
+ use FindDroplet
171
+ use CheckDropletActive
172
+ use SCPDroplet
173
+ end
174
+ end
175
+
151
176
  # Create a droplet
152
177
  def self.sequence_create_droplet
153
178
  ::Middleware::Builder.new do
@@ -218,6 +243,16 @@ module Tugboat
218
243
  end
219
244
  end
220
245
 
246
+ # Display a list of available SSH keys
247
+ def self.sequence_list_snapshots
248
+ ::Middleware::Builder.new do
249
+ use InjectConfiguration
250
+ use CheckConfiguration
251
+ use InjectClient
252
+ use ListSnapshots
253
+ end
254
+ end
255
+
221
256
  # Create a droplet
222
257
  def self.sequence_add_key
223
258
  ::Middleware::Builder.new do
@@ -0,0 +1,30 @@
1
+ module Tugboat
2
+ module Middleware
3
+ class BackupConfig < Base
4
+ def call(env)
5
+ ocean = env['droplet_kit']
6
+
7
+ if env['disable'] && env['enable']
8
+ say 'You cannot use both --disable and --enable for backup_config', :red
9
+ exit 1
10
+ end
11
+
12
+ begin
13
+ if env['disable']
14
+ response = ocean.droplet_actions.disable_backups(droplet_id: env['droplet_id'])
15
+ end
16
+ if env['enable']
17
+ response = ocean.droplet_actions.enable_backups(droplet_id: env['droplet_id'])
18
+ end
19
+ rescue DropletKit::Error => e
20
+ say "Failed to configure backups on droplet. Reason given from API: #{e}", :red
21
+ exit 1
22
+ end
23
+
24
+ say "Backup action #{response_stringify(response)} is #{response.status}"
25
+
26
+ @app.call(env)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -25,6 +25,10 @@ module Tugboat
25
25
  end
26
26
  end
27
27
 
28
+ def response_stringify(response)
29
+ response.type.gsub(/_/,' ')
30
+ end
31
+
28
32
  def call(env)
29
33
  @app.call(env)
30
34
  end
@@ -90,6 +94,66 @@ module Tugboat
90
94
  end
91
95
  end
92
96
 
97
+ def print_droplet_info(droplet, attribute, porcelain, include_urls, include_name)
98
+ droplet_ip4_public = droplet.networks.v4.find { |address| address.type == 'public' }.ip_address unless droplet.networks.v4.empty?
99
+ droplet_ip6_public = droplet.networks.v6.find { |address| address.type == 'public' }.ip_address unless droplet.networks.v6.empty?
100
+ check_private_ip = droplet.networks.v4.find { |address| address.type == 'private' }
101
+ droplet_private_ip = check_private_ip.ip_address if check_private_ip
102
+
103
+ attributes_list = [
104
+ ['name', droplet.name],
105
+ ['id', droplet.id],
106
+ ['status', droplet.status],
107
+ ['ip4', droplet_ip4_public],
108
+ ['ip6', droplet_ip6_public],
109
+ ['private_ip', droplet_private_ip],
110
+ ['region', droplet.region.slug],
111
+ ['image', droplet.image.id],
112
+ ['size', droplet.size_slug],
113
+ ['backups_active', !droplet.backup_ids.empty?]
114
+ ]
115
+ attributes = Hash[*attributes_list.flatten(1)]
116
+
117
+ if attribute
118
+ if attributes.key? attribute
119
+ if include_name
120
+ say "#{attributes['name']},#{attributes[attribute]}"
121
+ else
122
+ say attributes[attribute]
123
+ end
124
+ else
125
+ say "Invalid attribute \"#{attribute}\"", :red
126
+ say 'Provide one of the following:', :red
127
+ attributes_list.each { |a| say " #{a[0]}", :red }
128
+ exit 1
129
+ end
130
+ else
131
+ if porcelain
132
+ attributes_list.select { |a| !a[1].nil? }.each { |a| say "#{a[0]} #{a[1]}" }
133
+ say ""
134
+ else
135
+ print_droplet_info_full(droplet, include_urls)
136
+ end
137
+ end
138
+ end
139
+
140
+ def print_droplet_info_full(droplet, include_urls)
141
+ private_addr = droplet.networks.v4.find { |address| address.type == 'private' }
142
+ if private_addr
143
+ private_ip = ", private_ip: #{private_addr.ip_address}"
144
+ end
145
+
146
+ status_color = if droplet.status == 'active'
147
+ GREEN
148
+ else
149
+ RED
150
+ end
151
+
152
+ public_addr = droplet.networks.v4.find { |address| address.type == 'public' }
153
+
154
+ say "#{droplet.name} (ip: #{public_addr.ip_address}#{private_ip}, status: #{status_color}#{droplet.status}#{CLEAR}, region: #{droplet.region.slug}, size: #{droplet.size_slug}, id: #{droplet.id}#{include_urls ? droplet_id_to_url(droplet.id) : ''})"
155
+ end
156
+
93
157
  # Get all pages of droplets
94
158
  def get_droplet_list(ocean, per_page = 20)
95
159
  verify_credentials(ocean)
@@ -14,20 +14,7 @@ module Tugboat
14
14
  droplet_list.each do |droplet|
15
15
  has_one = true
16
16
 
17
- private_addr = droplet.networks.v4.find { |address| address.type == 'private' }
18
- if private_addr
19
- private_ip = ", private_ip: #{private_addr.ip_address}"
20
- end
21
-
22
- status_color = if droplet.status == 'active'
23
- GREEN
24
- else
25
- RED
26
- end
27
-
28
- public_addr = droplet.networks.v4.find { |address| address.type == 'public' }
29
-
30
- say "#{droplet.name} (ip: #{public_addr.ip_address}#{private_ip}, status: #{status_color}#{droplet.status}#{CLEAR}, region: #{droplet.region.slug}, size: #{droplet.size_slug}, id: #{droplet.id}#{env['include_urls'] ? droplet_id_to_url(droplet.id) : ''})"
17
+ print_droplet_info(droplet, env['attribute'], env['porcelain'], env['include_urls'], env['include_name'])
31
18
  end
32
19
 
33
20
  unless has_one
@@ -0,0 +1,28 @@
1
+ module Tugboat
2
+ module Middleware
3
+ # Check if the client has set-up configuration yet.
4
+ class ListSnapshots < Base
5
+ def call(env)
6
+ ocean = env['droplet_kit']
7
+
8
+ verify_credentials(ocean)
9
+
10
+ response = ocean.snapshots.all(per_page: env['per_page'])
11
+
12
+ has_one = false
13
+
14
+ response.each do |snapshot|
15
+ has_one = true
16
+
17
+ say "#{snapshot.name} (id: #{snapshot.id}, resource_type: #{snapshot.resource_type}, created_at: #{snapshot.created_at})"
18
+ end
19
+
20
+ unless has_one
21
+ say "You don't appear to have any snapshots.", :red
22
+ end
23
+
24
+ @app.call(env)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Tugboat
2
+ module Middleware
3
+ class SCPDroplet < Base
4
+ def call(env)
5
+ say "Executing SCP on Droplet #{env['droplet_name']}..."
6
+
7
+ identity = File.expand_path(env['config'].ssh_key_path.to_s).strip
8
+
9
+ ssh_user = env['user_droplet_ssh_user'] || env['config'].ssh_user
10
+
11
+ scp_command = env['user_scp_command'] || 'scp'
12
+
13
+ host_ip = env['droplet_ip']
14
+
15
+ host_string = "#{ssh_user}@#{host_ip}"
16
+
17
+ if env['user_droplet_ssh_wait']
18
+ say 'Wait flag given, waiting for droplet to become active'
19
+ wait_for_state(env['droplet_id'], 'active', env['barge'])
20
+ end
21
+
22
+ identity_string = "-i #{identity}"
23
+
24
+ scp_command_string = [scp_command, identity_string, env['user_from_file'], "#{host_string}:#{env['user_to_file']}"].join(' ')
25
+
26
+ say "Attempting SCP with `#{scp_command_string}`"
27
+
28
+ Kernel.exec(scp_command_string)
29
+
30
+ @app.call(env)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Tugboat
2
- VERSION = '4.0.0'.freeze
2
+ VERSION = '4.1.0'.freeze
3
3
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tugboat::CLI do
4
+ include_context 'spec'
5
+
6
+ describe 'backup_config' do
7
+ it 'enables backups on a droplet with the enable flag' do
8
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
9
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
10
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
11
+
12
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
13
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
14
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
15
+
16
+ stub_request(:post, 'https://api.digitalocean.com/v2/droplets/6918990/actions').
17
+ with(body: '{"type":"enable_backups"}',
18
+ headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
19
+ to_return(status: 200, body: fixture('enable_backups_response'), headers: {})
20
+
21
+ cli.options = cli.options.merge(enable: true)
22
+
23
+ expected_string = <<-eos
24
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
25
+ Backup action enable backups is in-progress
26
+ eos
27
+
28
+ expect { cli.backup_config('example.com') }.to output(expected_string).to_stdout
29
+ end
30
+
31
+ it 'enables backups on a droplet with the disable flag' do
32
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
33
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
34
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
35
+
36
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
37
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
38
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
39
+
40
+ stub_request(:post, 'https://api.digitalocean.com/v2/droplets/6918990/actions').
41
+ with(body: '{"type":"disable_backups"}',
42
+ headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
43
+ to_return(status: 200, body: fixture('disable_backups_response'), headers: {})
44
+
45
+ cli.options = cli.options.merge(disable: true)
46
+
47
+ expected_string = <<-eos
48
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
49
+ Backup action disable backups is in-progress
50
+ eos
51
+
52
+ expect { cli.backup_config('example.com') }.to output(expected_string).to_stdout
53
+ end
54
+
55
+ it 'shows error if both enable and disable given' do
56
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
57
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
58
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
59
+
60
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
61
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
62
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
63
+
64
+ cli.options = cli.options.merge(disable: true, enable: true)
65
+
66
+ expect { cli.backup_config('example.com') }.to raise_error(SystemExit).and output(%r{You cannot use both --disable and --enable for backup_config}).to_stdout
67
+ end
68
+ end
69
+ end
@@ -137,6 +137,154 @@ page2example3.com (ip: 104.236.32.173, status: \e[31moff\e[0m, region: nyc3, siz
137
137
  expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=2&per_page=3')).to have_been_made
138
138
  end
139
139
 
140
+ it 'gives porcelain output when set' do
141
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
142
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
143
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
144
+
145
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20').
146
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
147
+ to_return(status: 200, body: fixture('show_droplets'), headers: { 'Content-Type' => 'application/json' })
148
+
149
+ cli.options = cli.options.merge('porcelain' => true)
150
+
151
+ expected_string = <<-eos
152
+ name example.com
153
+ id 6918990
154
+ status active
155
+ ip4 104.236.32.182
156
+ ip6 2604:A880:0800:0010:0000:0000:02DD:4001
157
+ region nyc3
158
+ image 6918990
159
+ size 512mb
160
+ backups_active true
161
+
162
+ name example2.com
163
+ id 3164956
164
+ status active
165
+ ip4 104.236.32.172
166
+ ip6 2604:A880:0800:0010:0000:0000:02DD:4001
167
+ region nyc3
168
+ image 6918990
169
+ size 512mb
170
+ backups_active true
171
+
172
+ name example3.com
173
+ id 3164444
174
+ status off
175
+ ip4 104.236.32.173
176
+ ip6 2604:A880:0800:0010:0000:0000:02DD:4001
177
+ region nyc3
178
+ image 6918990
179
+ size 512mb
180
+ backups_active true
181
+
182
+ eos
183
+
184
+ expect { cli.droplets }.to output(expected_string).to_stdout
185
+
186
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.twice
187
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20')).to have_been_made
188
+ end
189
+
190
+ it 'gives ipv4 attribute output when set' do
191
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
192
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
193
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
194
+
195
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20').
196
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
197
+ to_return(status: 200, body: fixture('show_droplets'), headers: { 'Content-Type' => 'application/json' })
198
+
199
+ cli.options = cli.options.merge('attribute' => 'ip4', 'include_name' => true)
200
+
201
+ expected_string = <<-eos
202
+ example.com,104.236.32.182
203
+ example2.com,104.236.32.172
204
+ example3.com,104.236.32.173
205
+ eos
206
+
207
+ expect { cli.droplets }.to output(expected_string).to_stdout
208
+
209
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.twice
210
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20')).to have_been_made
211
+ end
212
+
213
+ it 'gives status attribute output when set' do
214
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
215
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
216
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
217
+
218
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20').
219
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
220
+ to_return(status: 200, body: fixture('show_droplets'), headers: { 'Content-Type' => 'application/json' })
221
+
222
+ cli.options = cli.options.merge('attribute' => 'status', 'include_name' => true)
223
+
224
+ expected_string = <<-eos
225
+ example.com,active
226
+ example2.com,active
227
+ example3.com,off
228
+ eos
229
+
230
+ expect { cli.droplets }.to output(expected_string).to_stdout
231
+
232
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.twice
233
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20')).to have_been_made
234
+ end
235
+
236
+ it 'gives only ip4 attribute output when set and include_name set to false' do
237
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
238
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
239
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
240
+
241
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20').
242
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
243
+ to_return(status: 200, body: fixture('show_droplets'), headers: { 'Content-Type' => 'application/json' })
244
+
245
+ cli.options = cli.options.merge('attribute' => 'ip4', 'include_name' => false)
246
+
247
+ expected_string = <<-eos
248
+ 104.236.32.182
249
+ 104.236.32.172
250
+ 104.236.32.173
251
+ eos
252
+
253
+ expect { cli.droplets }.to output(expected_string).to_stdout
254
+
255
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.twice
256
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20')).to have_been_made
257
+ end
258
+
259
+ it 'shows error if attribute asked for does not exist' do
260
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
261
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
262
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
263
+
264
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=20').
265
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
266
+ to_return(status: 200, body: fixture('show_droplets'), headers: { 'Content-Type' => 'application/json' })
267
+
268
+ cli.options = cli.options.merge('attribute' => 'foo')
269
+
270
+ expected_string = <<-eos
271
+ Invalid attribute \"foo\"
272
+ Provide one of the following:
273
+ name
274
+ id
275
+ status
276
+ ip4
277
+ ip6
278
+ private_ip
279
+ region
280
+ image
281
+ size
282
+ backups_active
283
+ eos
284
+
285
+ expect { cli.droplets }.to raise_error(SystemExit).and output(expected_string).to_stdout
286
+ end
287
+
140
288
  it 'shows error on failure in initial stage' do
141
289
  stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
142
290
  with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tugboat::CLI do
4
+ include_context 'spec'
5
+
6
+ describe 'scp' do
7
+ it "tries to fetch the droplet's IP from the API" do
8
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
9
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
10
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
11
+
12
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
13
+ to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
14
+ allow(Kernel).to receive(:exec).with("scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")
15
+
16
+ expected_string = <<-eos
17
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
18
+ Executing SCP on Droplet (example.com)...
19
+ Attempting SCP with `scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
20
+ eos
21
+
22
+ expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
23
+ end
24
+
25
+ it "runs with rsync if given at the command line" do
26
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
27
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
28
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
29
+
30
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
31
+ to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
32
+ allow(Kernel).to receive(:exec).with("rsync -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")
33
+
34
+ expected_string = <<-eos
35
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
36
+ Executing SCP on Droplet (example.com)...
37
+ Attempting SCP with `rsync -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
38
+ eos
39
+
40
+ cli.options = cli.options.merge(scp_command: 'rsync')
41
+
42
+ expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
43
+ end
44
+
45
+ it "wait's until droplet active if -w command is given and droplet eventually active" do
46
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
47
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
48
+ to_return(status: 200, body: '', headers: {})
49
+
50
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets/6918990?per_page=200').
51
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
52
+ to_return(
53
+ { status: 200, body: fixture('show_droplet_inactive'), headers: {} },
54
+ status: 200, body: fixture('show_droplet'), headers: {}
55
+ )
56
+
57
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=200').
58
+ to_return(headers: { 'Content-Type' => 'application/json' }, status: 200, body: fixture('show_droplets'))
59
+ allow(Kernel).to receive(:exec).with("scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar")
60
+
61
+ cli.options = cli.options.merge(wait: true)
62
+
63
+ expected_string = <<-eos
64
+ Droplet fuzzy name provided. Finding droplet ID...done\e[0m, 6918990 (example.com)
65
+ Executing SCP on Droplet (example.com)...
66
+ Wait flag given, waiting for droplet to become active
67
+ ..done\e[0m (2s)
68
+ Attempting SCP with `scp -i #{Dir.home}/.ssh/id_rsa2 /tmp/foo baz@104.236.32.182:/tmp/bar`
69
+ eos
70
+
71
+ expect { cli.scp('example.com', '/tmp/foo', '/tmp/bar') }.to output(expected_string).to_stdout
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tugboat::CLI do
4
+ include_context 'spec'
5
+
6
+ describe 'snapshots' do
7
+ it 'shows a list when snapshots exist' do
8
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
9
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
10
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
11
+
12
+ stub_request(:get, "https://api.digitalocean.com/v2/snapshots?page=1&per_page=20").
13
+ with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer foo', 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
14
+ to_return(status: 200, body: fixture('show_snapshots'), headers: {})
15
+
16
+ expected_string = <<-eos
17
+ 5.10 x64 (id: 6372321, resource_type: droplet, created_at: 2014-09-26T16:40:18Z)
18
+ eos
19
+
20
+ expect { cli.snapshots }.to output(expected_string).to_stdout
21
+
22
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.once
23
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/snapshots?page=1&per_page=20')).to have_been_made.once
24
+ end
25
+
26
+ it 'shows a message when no snapshots exist' do
27
+ stub_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1').
28
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer foo', 'Content-Type' => 'application/json', 'User-Agent' => 'Faraday v0.9.2' }).
29
+ to_return(status: 200, body: fixture('show_droplets'), headers: {})
30
+
31
+ stub_request(:get, "https://api.digitalocean.com/v2/snapshots?page=1&per_page=20").
32
+ with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer foo', 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
33
+ to_return(status: 200, body: fixture('show_snapshots_empty'), headers: {})
34
+
35
+ expected_string = <<-eos
36
+ You don't appear to have any snapshots.
37
+ eos
38
+
39
+ expect { cli.snapshots }.to output(expected_string).to_stdout
40
+
41
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/droplets?page=1&per_page=1')).to have_been_made.once
42
+ expect(a_request(:get, 'https://api.digitalocean.com/v2/snapshots?page=1&per_page=20')).to have_been_made.once
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ {
2
+ "action": {
3
+ "id": 2,
4
+ "status": "in-progress",
5
+ "type": "disable_backups",
6
+ "started_at": "2014-07-29T14:35:27Z",
7
+ "completed_at": null,
8
+ "resource_id": 12,
9
+ "resource_type": "droplet",
10
+ "region_slug": "nyc1",
11
+ "region": {
12
+ "name": "New York",
13
+ "slug": "nyc1",
14
+ "available": true,
15
+ "sizes": [
16
+ "512mb"
17
+ ],
18
+ "features": [
19
+ "virtio",
20
+ "private_networking",
21
+ "backups",
22
+ "ipv6",
23
+ "metadata"
24
+ ]
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "action": {
3
+ "id": 2,
4
+ "status": "in-progress",
5
+ "type": "enable_backups",
6
+ "started_at": "2014-07-29T14:35:27Z",
7
+ "completed_at": null,
8
+ "resource_id": 12,
9
+ "resource_type": "droplet",
10
+ "region_slug": "nyc1",
11
+ "region": {
12
+ "name": "New York",
13
+ "slug": "nyc1",
14
+ "available": true,
15
+ "sizes": [
16
+ "512mb"
17
+ ],
18
+ "features": [
19
+ "virtio",
20
+ "private_networking",
21
+ "backups",
22
+ "ipv6",
23
+ "metadata"
24
+ ]
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "snapshots": [
3
+ {
4
+ "id": 6372321,
5
+ "name": "5.10 x64",
6
+ "regions": [
7
+ "nyc1",
8
+ "ams1",
9
+ "sfo1",
10
+ "nyc2",
11
+ "ams2",
12
+ "sgp1",
13
+ "lon1",
14
+ "nyc3",
15
+ "ams3",
16
+ "fra1",
17
+ "tor1"
18
+ ],
19
+ "created_at": "2014-09-26T16:40:18Z",
20
+ "resource_id": 2713828,
21
+ "resource_type": "droplet",
22
+ "min_disk_size": 20,
23
+ "size_gigabytes": 1.42
24
+ }
25
+ ],
26
+ "links": {
27
+ "pages": {
28
+ "last": "https://api.digitalocean.com/v2/snapshots?page=1&per_page=1"
29
+ }
30
+ },
31
+ "meta": {
32
+ "total": 1
33
+ }
34
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "snapshots": [],
3
+ "links": {},
4
+ "meta": {
5
+ "total": 0
6
+ }
7
+ }
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tugboat
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Pearkes
8
8
  - Peter Souter
9
- - Ørjan Blom
9
+ - "Ørjan Blom"
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-12-03 00:00:00.000000000 Z
13
+ date: 2018-03-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: thor
@@ -396,6 +396,7 @@ files:
396
396
  - lib/tugboat/middleware.rb
397
397
  - lib/tugboat/middleware/add_key.rb
398
398
  - lib/tugboat/middleware/ask_for_credentials.rb
399
+ - lib/tugboat/middleware/backup_setting.rb
399
400
  - lib/tugboat/middleware/base.rb
400
401
  - lib/tugboat/middleware/check_configuration.rb
401
402
  - lib/tugboat/middleware/check_credentials.rb
@@ -419,11 +420,13 @@ files:
419
420
  - lib/tugboat/middleware/list_images.rb
420
421
  - lib/tugboat/middleware/list_regions.rb
421
422
  - lib/tugboat/middleware/list_sizes.rb
423
+ - lib/tugboat/middleware/list_snapshots.rb
422
424
  - lib/tugboat/middleware/list_ssh_keys.rb
423
425
  - lib/tugboat/middleware/password_reset.rb
424
426
  - lib/tugboat/middleware/rebuild_droplet.rb
425
427
  - lib/tugboat/middleware/resize_droplet.rb
426
428
  - lib/tugboat/middleware/restart_droplet.rb
429
+ - lib/tugboat/middleware/scp_droplet.rb
427
430
  - lib/tugboat/middleware/snapshot_droplet.rb
428
431
  - lib/tugboat/middleware/ssh_droplet.rb
429
432
  - lib/tugboat/middleware/start_droplet.rb
@@ -432,6 +435,7 @@ files:
432
435
  - license/dependency_decisions.yml
433
436
  - spec/cli/add_key_spec.rb
434
437
  - spec/cli/authorize_cli_spec.rb
438
+ - spec/cli/backup_setting_spec.rb
435
439
  - spec/cli/config_cli_spec.rb
436
440
  - spec/cli/create_cli_spec.rb
437
441
  - spec/cli/debug_cli_spec.rb
@@ -450,8 +454,10 @@ files:
450
454
  - spec/cli/regions_cli_spec.rb
451
455
  - spec/cli/resize_cli_spec.rb
452
456
  - spec/cli/restart_cli_spec.rb
457
+ - spec/cli/scp_cli_spec.rb
453
458
  - spec/cli/sizes_cli_spec.rb
454
459
  - spec/cli/snapshot_cli_spec.rb
460
+ - spec/cli/snapshots_cli_spec.rb
455
461
  - spec/cli/ssh_cli_spec.rb
456
462
  - spec/cli/start_cli_spec.rb
457
463
  - spec/cli/verify_cli_spec.rb
@@ -463,8 +469,10 @@ files:
463
469
  - spec/fixtures/create_droplet.json
464
470
  - spec/fixtures/create_ssh_key.json
465
471
  - spec/fixtures/create_ssh_key_from_file.json
472
+ - spec/fixtures/disable_backups_response.json
466
473
  - spec/fixtures/droplet_no_network.json
467
474
  - spec/fixtures/droplet_start_response.json
475
+ - spec/fixtures/enable_backups_response.json
468
476
  - spec/fixtures/not_found.json
469
477
  - spec/fixtures/password_reset_response.json
470
478
  - spec/fixtures/power_cycle_response.json
@@ -487,6 +495,8 @@ files:
487
495
  - spec/fixtures/show_redmine_image.json
488
496
  - spec/fixtures/show_regions.json
489
497
  - spec/fixtures/show_sizes.json
498
+ - spec/fixtures/show_snapshots.json
499
+ - spec/fixtures/show_snapshots_empty.json
490
500
  - spec/fixtures/shutdown_response.json
491
501
  - spec/fixtures/snapshot_response.json
492
502
  - spec/fixtures/ubuntu_image_9801951.json
@@ -524,7 +534,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
524
534
  version: '0'
525
535
  requirements: []
526
536
  rubyforge_project:
527
- rubygems_version: 2.5.1
537
+ rubygems_version: 2.4.5
528
538
  signing_key:
529
539
  specification_version: 4
530
540
  summary: A command line tool for interacting with your DigitalOcean droplets.
@@ -538,6 +548,7 @@ test_files:
538
548
  - features/tugboat/config_number_key.feature
539
549
  - spec/cli/add_key_spec.rb
540
550
  - spec/cli/authorize_cli_spec.rb
551
+ - spec/cli/backup_setting_spec.rb
541
552
  - spec/cli/config_cli_spec.rb
542
553
  - spec/cli/create_cli_spec.rb
543
554
  - spec/cli/debug_cli_spec.rb
@@ -556,8 +567,10 @@ test_files:
556
567
  - spec/cli/regions_cli_spec.rb
557
568
  - spec/cli/resize_cli_spec.rb
558
569
  - spec/cli/restart_cli_spec.rb
570
+ - spec/cli/scp_cli_spec.rb
559
571
  - spec/cli/sizes_cli_spec.rb
560
572
  - spec/cli/snapshot_cli_spec.rb
573
+ - spec/cli/snapshots_cli_spec.rb
561
574
  - spec/cli/ssh_cli_spec.rb
562
575
  - spec/cli/start_cli_spec.rb
563
576
  - spec/cli/verify_cli_spec.rb
@@ -569,8 +582,10 @@ test_files:
569
582
  - spec/fixtures/create_droplet.json
570
583
  - spec/fixtures/create_ssh_key.json
571
584
  - spec/fixtures/create_ssh_key_from_file.json
585
+ - spec/fixtures/disable_backups_response.json
572
586
  - spec/fixtures/droplet_no_network.json
573
587
  - spec/fixtures/droplet_start_response.json
588
+ - spec/fixtures/enable_backups_response.json
574
589
  - spec/fixtures/not_found.json
575
590
  - spec/fixtures/password_reset_response.json
576
591
  - spec/fixtures/power_cycle_response.json
@@ -593,6 +608,8 @@ test_files:
593
608
  - spec/fixtures/show_redmine_image.json
594
609
  - spec/fixtures/show_regions.json
595
610
  - spec/fixtures/show_sizes.json
611
+ - spec/fixtures/show_snapshots.json
612
+ - spec/fixtures/show_snapshots_empty.json
596
613
  - spec/fixtures/shutdown_response.json
597
614
  - spec/fixtures/snapshot_response.json
598
615
  - spec/fixtures/ubuntu_image_9801951.json