brightbox-cli 4.2.0 → 4.3.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.
data/locales/en.yml CHANGED
@@ -136,6 +136,8 @@ en:
136
136
  update:
137
137
  desc: Update a load balancer
138
138
  long_desc: All intervals and timeouts are in milliseconds
139
+ login:
140
+ desc: Authenticate using an email address
139
141
  servers:
140
142
  desc: Manage an account's servers
141
143
  activate_console:
@@ -153,7 +155,7 @@ en:
153
155
  reboot:
154
156
  desc: Reboot servers (OS reboot issued)
155
157
  reset:
156
- desc: Reset servers (Hardward reset issued)
158
+ desc: Reset servers (Hardware reset issued)
157
159
  show:
158
160
  desc: Show servers
159
161
  shutdown:
@@ -243,3 +245,59 @@ en:
243
245
  desc: Show users
244
246
  update:
245
247
  desc: Update a user
248
+ volumes:
249
+ desc: Manage an account's volumes
250
+ args:
251
+ one: <volume>
252
+ optional: [<volume>...]
253
+ many: <volume>...
254
+ specify_one_id_first: You must specify the volume ID as the first argument
255
+ specify_many_ids: You must specify volume IDs as arguments
256
+ unknown_id: Couldn't find %{volume}
257
+ options:
258
+ boot: Set this volume as boot volume on server
259
+ delete_with_server: Set volume to be deleted with a server if attached
260
+ encrypted: Encrypt the volume
261
+ fs_label: Filesystem label, visible from within some OS
262
+ fs_type: Filesystem type to use to create blank volume
263
+ image: Create volume from existing Image ID
264
+ serial: Customisable serial number for volume
265
+ size: Volume size in MB
266
+ attach:
267
+ desc: Attach a volume to an existing server
268
+ args: <volume> <server>
269
+ specify_server_id_second: "You must specify the server ID to attach to as the second argument"
270
+ acting: Attaching %{volume}
271
+ copy:
272
+ desc: Create a new volume from an existing one
273
+ acting: Copying %{volume}
274
+ create:
275
+ desc: Create a new volume
276
+ acting: Creating volume
277
+ image_or_type_required: An 'image' or 'fs-type' option must be passed
278
+ either_image_or_type: An 'image' and 'fs-type' can not be passed together
279
+ destroy:
280
+ desc: Destroy one or more volumes
281
+ acting: Destroying %{volume}
282
+ detach:
283
+ desc: Detach a volume from a server
284
+ acting: Detaching %{volume}
285
+ list:
286
+ desc: List all volumes or limit using passed IDs
287
+ lock:
288
+ desc: Lock volumes
289
+ acting: Locking %{volume}
290
+ resize:
291
+ desc: Resize a volume
292
+ size_option_needed: A 'size' option is required
293
+ acting: Resizing %{volume}
294
+ show:
295
+ desc: Show details about multiple volumes
296
+ unlock:
297
+ desc: Unlock volumes
298
+ acting: Unlocking %{volume}
299
+ update:
300
+ desc: Update a volume
301
+ acting: Updating %{volume}
302
+
303
+
@@ -0,0 +1,165 @@
1
+ require "spec_helper"
2
+
3
+ describe "brightbox volumes attach" do
4
+ include VolumeHelpers
5
+
6
+ let(:output) { FauxIO.new { Brightbox.run(argv) } }
7
+ let(:stdout) { output.stdout }
8
+ let(:stderr) { output.stderr }
9
+
10
+ before do
11
+ config_from_contents(API_CLIENT_CONFIG_CONTENTS)
12
+
13
+ stub_request(:post, "http://api.brightbox.localhost/token")
14
+ .to_return(
15
+ status: 200,
16
+ body: JSON.dump(
17
+ access_token: "ACCESS-TOKEN",
18
+ refresh_token: "REFRESH_TOKEN"
19
+ )
20
+ )
21
+
22
+ Brightbox.config.reauthenticate
23
+ end
24
+
25
+ context "without arguments" do
26
+ let(:argv) { %w[volumes attach] }
27
+
28
+ it "does not error" do
29
+ expect { output }.to_not raise_error
30
+
31
+ expect(stderr).to eq("ERROR: You must specify the volume ID as the first argument\n")
32
+
33
+ expect(stdout).to match("")
34
+ end
35
+ end
36
+
37
+ context "without server arguments" do
38
+ let(:argv) { %w[volumes attach vol-32145] }
39
+
40
+ it "does not error" do
41
+ expect { output }.to_not raise_error
42
+
43
+ expect(stderr).to eq("ERROR: You must specify the server ID to attach to as the second argument\n")
44
+
45
+ expect(stdout).to match("")
46
+ end
47
+ end
48
+
49
+ context "with arguments" do
50
+ let(:argv) { %w[volumes attach vol-809s1 srv-03431] }
51
+
52
+ before do
53
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-809s1")
54
+ .with(query: hash_including(account_id: "acc-12345"))
55
+ .to_return(
56
+ status: 200,
57
+ body: volume_response(
58
+ id: "vol-809s1",
59
+ status: "detached",
60
+ boot: false,
61
+ server: nil
62
+ )
63
+ )
64
+ .to_return(
65
+ status: 200,
66
+ body: volume_response(
67
+ id: "vol-809s1",
68
+ status: "attached",
69
+ boot: false,
70
+ server: {
71
+ id: "srv-03431"
72
+ }
73
+ )
74
+ )
75
+
76
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes/vol-809s1/attach")
77
+ .with(query: hash_including(account_id: "acc-12345"),
78
+ body: hash_including(
79
+ server: "srv-03431",
80
+ boot: false
81
+ ))
82
+ .to_return(
83
+ status: 202,
84
+ body: volume_response(
85
+ id: "vol-809s1",
86
+ status: "attached",
87
+ server: {
88
+ id: "srv-03431"
89
+ }
90
+ )
91
+ )
92
+ end
93
+
94
+ it "does not error" do
95
+ expect { output }.to_not raise_error
96
+
97
+ expect(stderr).to eq("Attaching vol-809s1\n")
98
+
99
+ aggregate_failures do
100
+ expect(stdout).to match("vol-809s1")
101
+ expect(stdout).to match("attached")
102
+ expect(stdout).to match("false")
103
+ end
104
+ end
105
+ end
106
+
107
+ context "with boot option" do
108
+ let(:argv) { %w[volumes attach --boot vol-90328 srv-03431] }
109
+
110
+ before do
111
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-90328")
112
+ .with(query: hash_including(account_id: "acc-12345"))
113
+ .to_return(
114
+ status: 200,
115
+ body: volume_response(
116
+ id: "vol-90328",
117
+ status: "detached",
118
+ boot: false,
119
+ server: nil
120
+ )
121
+ )
122
+ .to_return(
123
+ status: 200,
124
+ body: volume_response(
125
+ id: "vol-90328",
126
+ status: "attached",
127
+ boot: true,
128
+ server: {
129
+ id: "srv-03431"
130
+ }
131
+ )
132
+ )
133
+
134
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes/vol-90328/attach")
135
+ .with(query: hash_including(account_id: "acc-12345"),
136
+ body: hash_including(
137
+ server: "srv-03431",
138
+ boot: true
139
+ ))
140
+ .to_return(
141
+ status: 202,
142
+ body: volume_response(
143
+ id: "vol-90328",
144
+ boot: true,
145
+ status: "attached",
146
+ server: {
147
+ id: "srv-03431"
148
+ }
149
+ )
150
+ )
151
+ end
152
+
153
+ it "does not error" do
154
+ expect { output }.to_not raise_error
155
+
156
+ expect(stderr).to eq("Attaching vol-90328\n")
157
+
158
+ aggregate_failures do
159
+ expect(stdout).to match("vol-90328")
160
+ expect(stdout).to match("attached")
161
+ expect(stdout).to match("true")
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+
3
+ describe "brightbox volumes copy" do
4
+ include VolumeHelpers
5
+
6
+ let(:output) { FauxIO.new { Brightbox.run(argv) } }
7
+ let(:stdout) { output.stdout }
8
+ let(:stderr) { output.stderr }
9
+
10
+ before do
11
+ config_from_contents(API_CLIENT_CONFIG_CONTENTS)
12
+
13
+ stub_request(:post, "http://api.brightbox.localhost/token")
14
+ .to_return(
15
+ status: 200,
16
+ body: JSON.dump(
17
+ access_token: "ACCESS-TOKEN",
18
+ refresh_token: "REFRESH_TOKEN"
19
+ )
20
+ )
21
+
22
+ Brightbox.config.reauthenticate
23
+ end
24
+
25
+ context "without arguments" do
26
+ let(:argv) { %w[volumes copy] }
27
+
28
+ it "does not error" do
29
+ expect { output }.to_not raise_error
30
+
31
+ expect(stderr).to eq("ERROR: You must specify the volume ID as the first argument\n")
32
+
33
+ expect(stdout).to match("")
34
+ end
35
+ end
36
+
37
+ context "with argument" do
38
+ let(:argv) { %w[volumes copy vol-909ds] }
39
+
40
+ before do
41
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-909ds")
42
+ .with(query: hash_including(account_id: "acc-12345"))
43
+ .to_return(
44
+ status: 200,
45
+ body: volume_response(
46
+ id: "vol-909ds"
47
+ )
48
+ )
49
+ .to_return(
50
+ status: 200,
51
+ body: volume_response(
52
+ id: "vol-909ds"
53
+ )
54
+ )
55
+
56
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes/vol-909ds/copy")
57
+ .with(query: hash_including(account_id: "acc-12345"))
58
+ .to_return(
59
+ status: 202,
60
+ body: volume_response(
61
+ id: "vol-909ds"
62
+ )
63
+ )
64
+ end
65
+
66
+ it "does not error" do
67
+ expect { output }.to_not raise_error
68
+
69
+ expect(stderr).to eq("Copying vol-909ds\n")
70
+
71
+ expect(stdout).to match("vol-909ds")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,217 @@
1
+ require "spec_helper"
2
+
3
+ describe "brightbox volumes create" do
4
+ include VolumeHelpers
5
+
6
+ let(:output) { FauxIO.new { Brightbox.run(argv) } }
7
+ let(:stdout) { output.stdout }
8
+ let(:stderr) { output.stderr }
9
+
10
+ let(:token) { SecureRandom.hex }
11
+
12
+ before do
13
+ config_from_contents(API_CLIENT_CONFIG_CONTENTS)
14
+
15
+ stub_request(:post, "http://api.brightbox.localhost/token")
16
+ .to_return(status: 200, body: JSON.dump(access_token: token))
17
+
18
+ Brightbox.config.reauthenticate
19
+ end
20
+
21
+ context "without options" do
22
+ let(:argv) { %w[volumes create] }
23
+
24
+ it "does not error" do
25
+ expect { output }.to_not raise_error
26
+
27
+ expect(stderr).to match("ERROR: An 'image' or 'fs-type' option must be passed")
28
+
29
+ expect(stdout).to eq("")
30
+ end
31
+ end
32
+
33
+ context "with mutually exclusive options" do
34
+ let(:argv) { %w[volumes create --image img-12345 --fs-type ext4] }
35
+
36
+ it "does not error" do
37
+ expect { output }.to_not raise_error
38
+
39
+ expect(stderr).to match("ERROR: An 'image' and 'fs-type' can not be passed together")
40
+
41
+ expect(stdout).to eq("")
42
+ end
43
+ end
44
+
45
+ context "with image identifier" do
46
+ let(:argv) { %w[volumes create --image img-12345] }
47
+
48
+ before do
49
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes")
50
+ .with(headers: { "Content-Type" => "application/json" },
51
+ query: hash_including(account_id: "acc-12345"),
52
+ body: {
53
+ delete_with_server: false,
54
+ encrypted: false,
55
+ image: "img-12345"
56
+ })
57
+ .to_return(
58
+ status: 202,
59
+ body: volume_response(
60
+ id: "vol-12345",
61
+ storage_type: "network",
62
+ size: 10_240,
63
+ status: "detached"
64
+ )
65
+ )
66
+ end
67
+
68
+ it "does not error" do
69
+ expect { output }.to_not raise_error
70
+
71
+ expect(stderr).not_to match("ERROR")
72
+
73
+ aggregate_failures do
74
+ expect(stdout).to match("id.*type.*size.*status.*server.*boot")
75
+
76
+ expect(stdout).to match("vol-12345")
77
+ expect(stdout).to match("network")
78
+ expect(stdout).to match("10240")
79
+ expect(stdout).to match("detached")
80
+ expect(stdout).to match("false")
81
+ end
82
+ end
83
+ end
84
+
85
+ context "with image and size" do
86
+ let(:argv) { %w[volumes create --image img-12345 --size 20480] }
87
+
88
+ before do
89
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes")
90
+ .with(headers: { "Content-Type" => "application/json" },
91
+ query: hash_including(account_id: "acc-12345"),
92
+ body: {
93
+ delete_with_server: false,
94
+ encrypted: false,
95
+ image: "img-12345",
96
+ size: 20_480
97
+ })
98
+ .to_return(
99
+ status: 202,
100
+ body: volume_response(
101
+ id: "vol-12345",
102
+ storage_type: "network",
103
+ size: 20_480,
104
+ status: "detached"
105
+ )
106
+ )
107
+ end
108
+
109
+ it "does not error" do
110
+ expect { output }.to_not raise_error
111
+
112
+ expect(stderr).not_to match("ERROR")
113
+
114
+ aggregate_failures do
115
+ expect(stdout).to match("id.*type.*size.*status.*server.*boot")
116
+
117
+ expect(stdout).to match("vol-12345")
118
+ expect(stdout).to match("network")
119
+ expect(stdout).to match("20480")
120
+ expect(stdout).to match("detached")
121
+ expect(stdout).to match("false")
122
+ end
123
+ end
124
+ end
125
+
126
+ context "with filesystem type" do
127
+ let(:argv) { %w[volumes create --fs-type ext4] }
128
+
129
+ before do
130
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes")
131
+ .with(headers: { "Content-Type" => "application/json" },
132
+ query: hash_including(account_id: "acc-12345"),
133
+ body: {
134
+ delete_with_server: false,
135
+ encrypted: false,
136
+ filesystem_type: "ext4"
137
+ })
138
+ .to_return(
139
+ status: 202,
140
+ body: volume_response(
141
+ id: "vol-12345",
142
+ storage_type: "network",
143
+ size: 10_240,
144
+ status: "detached"
145
+ )
146
+ )
147
+ end
148
+
149
+ it "does not error" do
150
+ expect { output }.to_not raise_error
151
+
152
+ expect(stderr).not_to match("ERROR")
153
+
154
+ aggregate_failures do
155
+ expect(stdout).to match("id.*type.*size.*status.*server.*boot")
156
+
157
+ expect(stdout).to match("vol-12345")
158
+ expect(stdout).to match("network")
159
+ expect(stdout).to match("10240")
160
+ expect(stdout).to match("detached")
161
+ expect(stdout).to match("false")
162
+ end
163
+ end
164
+ end
165
+
166
+ context "with most options" do
167
+ let(:argv) do
168
+ %w[volumes create --fs-label fnord --fs-type ext4 --delete-with-server --encrypted --name Nom -d Test --serial 12345 -s 20480]
169
+ end
170
+
171
+ before do
172
+ stub_request(:post, "http://api.brightbox.localhost/1.0/volumes")
173
+ .with(headers: { "Content-Type" => "application/json" },
174
+ query: hash_including(account_id: "acc-12345"),
175
+ body: {
176
+ delete_with_server: true,
177
+ description: "Test",
178
+ encrypted: true,
179
+ filesystem_label: "fnord",
180
+ filesystem_type: "ext4",
181
+ name: "Nom",
182
+ serial: "12345",
183
+ size: 20_480
184
+ })
185
+ .to_return(
186
+ status: 202,
187
+ body: volume_response(
188
+ id: "vol-43251",
189
+ name: "Nom",
190
+ boot: false,
191
+ description: "Test",
192
+ encrypted: true,
193
+ serial: "12345",
194
+ storage_type: "network",
195
+ size: 20_480,
196
+ status: "detached"
197
+ )
198
+ )
199
+ end
200
+
201
+ it "does not error" do
202
+ expect { output }.to_not raise_error
203
+
204
+ expect(stderr).not_to match("ERROR")
205
+
206
+ aggregate_failures do
207
+ expect(stdout).to match("id.*type.*size.*status.*server.*boot")
208
+
209
+ expect(stdout).to match("vol-43251")
210
+ expect(stdout).to match("network")
211
+ expect(stdout).to match("20480")
212
+ expect(stdout).to match("detached")
213
+ expect(stdout).to match("false")
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,173 @@
1
+ require "spec_helper"
2
+
3
+ describe "brightbox volumes destroy" do
4
+ include VolumeHelpers
5
+
6
+ let(:output) { FauxIO.new { Brightbox.run(argv) } }
7
+ let(:stdout) { output.stdout }
8
+ let(:stderr) { output.stderr }
9
+
10
+ before do
11
+ config_from_contents(API_CLIENT_CONFIG_CONTENTS)
12
+
13
+ stub_request(:post, "http://api.brightbox.localhost/token")
14
+ .to_return(
15
+ status: 200,
16
+ body: JSON.dump(
17
+ access_token: "ACCESS-TOKEN",
18
+ refresh_token: "REFRESH_TOKEN"
19
+ )
20
+ )
21
+
22
+ Brightbox.config.reauthenticate
23
+ end
24
+
25
+ context "without arguments" do
26
+ let(:argv) { %w[volumes destroy] }
27
+
28
+ it "does not error" do
29
+ expect { output }.to_not raise_error
30
+
31
+ expect(stderr).to eq("ERROR: You must specify volume IDs as arguments\n")
32
+
33
+ expect(stdout).to match("")
34
+ end
35
+ end
36
+
37
+ context "with one argument" do
38
+ let(:argv) { %w[volumes destroy vol-ui342] }
39
+
40
+ before do
41
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-ui342")
42
+ .with(query: hash_including(account_id: "acc-12345"))
43
+ .to_return(
44
+ status: 200,
45
+ body: volume_response(
46
+ id: "vol-ui342",
47
+ status: "detached",
48
+ boot: false,
49
+ server: {
50
+ id: "srv-03431"
51
+ }
52
+ )
53
+ )
54
+ .to_return(
55
+ status: 200,
56
+ body: volume_response(
57
+ id: "vol-ui342",
58
+ status: "detached",
59
+ boot: false,
60
+ server: nil
61
+ )
62
+ )
63
+
64
+ stub_request(:delete, "http://api.brightbox.localhost/1.0/volumes/vol-ui342")
65
+ .with(query: hash_including(account_id: "acc-12345"))
66
+ .to_return(
67
+ status: 202,
68
+ body: volume_response(
69
+ id: "vol-ui342",
70
+ status: "detached",
71
+ server: {
72
+ id: "srv-03431"
73
+ }
74
+ )
75
+ )
76
+ end
77
+
78
+ it "does not error" do
79
+ expect { output }.to_not raise_error
80
+
81
+ expect(stderr).to match("Destroying vol-ui342\n")
82
+
83
+ expect(stdout).to eq("")
84
+ end
85
+ end
86
+
87
+ context "with multiple arguments" do
88
+ let(:argv) { %w[volumes destroy vol-o2342 vol-3422f] }
89
+
90
+ before do
91
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-o2342")
92
+ .with(query: hash_including(account_id: "acc-12345"))
93
+ .to_return(
94
+ status: 200,
95
+ body: volume_response(
96
+ id: "vol-o2342",
97
+ status: "detached",
98
+ boot: false,
99
+ server: {
100
+ id: "srv-03431"
101
+ }
102
+ )
103
+ )
104
+ .to_return(
105
+ status: 200,
106
+ body: volume_response(
107
+ id: "vol-o2342",
108
+ status: "detached",
109
+ boot: false,
110
+ server: nil
111
+ )
112
+ )
113
+
114
+ stub_request(:delete, "http://api.brightbox.localhost/1.0/volumes/vol-o2342")
115
+ .with(query: hash_including(account_id: "acc-12345"))
116
+ .to_return(
117
+ status: 202,
118
+ body: volume_response(
119
+ id: "vol-o2342",
120
+ status: "detached",
121
+ server: {
122
+ id: "srv-03431"
123
+ }
124
+ )
125
+ )
126
+
127
+ stub_request(:get, "http://api.brightbox.localhost/1.0/volumes/vol-3422f")
128
+ .with(query: hash_including(account_id: "acc-12345"))
129
+ .to_return(
130
+ status: 200,
131
+ body: volume_response(
132
+ id: "vol-3422f",
133
+ status: "detached",
134
+ boot: false,
135
+ server: {
136
+ id: "srv-03431"
137
+ }
138
+ )
139
+ )
140
+ .to_return(
141
+ status: 200,
142
+ body: volume_response(
143
+ id: "vol-3422f",
144
+ status: "detached",
145
+ boot: false,
146
+ server: nil
147
+ )
148
+ )
149
+
150
+ stub_request(:delete, "http://api.brightbox.localhost/1.0/volumes/vol-3422f")
151
+ .with(query: hash_including(account_id: "acc-12345"))
152
+ .to_return(
153
+ status: 202,
154
+ body: volume_response(
155
+ id: "vol-3422f",
156
+ status: "detached",
157
+ server: {
158
+ id: "srv-03431"
159
+ }
160
+ )
161
+ )
162
+ end
163
+
164
+ it "does not error" do
165
+ expect { output }.to_not raise_error
166
+
167
+ expect(stderr).to match("Destroying vol-o2342\n")
168
+ expect(stderr).to match("Destroying vol-3422f\n")
169
+
170
+ expect(stdout).to eq("")
171
+ end
172
+ end
173
+ end