tugboat 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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