ruby-triton 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ec6c7ecda93e72915f6370e0b5c8d89622a6993
4
+ data.tar.gz: ed330e27e36e6272b66360ae0a05b57c73982796
5
+ SHA512:
6
+ metadata.gz: ed8ef30ad0b7f605c6ff9c1ce5bd77b70e52a579f437c2cac38ba9f321f2bf37253306c0030114a9e9668f3ab8c93cce7ab94f36850d5194a7837a4eaf5cd241
7
+ data.tar.gz: 425d74abe4b5291e26d71db8e3172941ba824c35f7f90e953127c99537bdba995d5fda4890704134c1c1c4ef05812bf560fcec63119acc77d09c9fcd5ec044b9
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT) Copyright (c) 2016 Les Technologies Alesium Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ ruby-triton
2
+ ===========
3
+
4
+ What's ruby-triton?
5
+ ------------------
6
+
7
+ ruby-triton is a client for communicating with cloudapi from Triton container
8
+ services to interact with the datacenter.
9
+
10
+ Acknolegement
11
+ =============
12
+
13
+ Joyent for creating Triton cloudapi
14
+ @dekobon for creating ruby-manta which was used as the base for this.
15
+
16
+
17
+ License
18
+ =======
19
+
20
+ (c) 2016 Les Technologies Alesium Inc, licensed under MIT. See LICENSE for
21
+ details, you legal geek you.
@@ -0,0 +1,17 @@
1
+ require_relative 'lib/ruby-triton'
2
+
3
+ host = ENV['SDC_URL']
4
+ account = ENV['SDC_ACCOUNT']
5
+ #subuser = ENV['SDC_USER']
6
+ priv_key = ENV['SDC_KEY' ]
7
+
8
+ priv_key_data = File.read(priv_key)
9
+
10
+ client = RubyTriton::CloudApiClient.new(host, account, priv_key_data,
11
+ :verify_ssl => false,
12
+ # :subuser => 'monte',
13
+ )
14
+
15
+ client.list_machines(:name => "lala").each do | instances |
16
+ puts instances.inspect
17
+ end
@@ -0,0 +1,5 @@
1
+ module RubyTriton
2
+ end
3
+ require_relative 'ruby-triton/cloudapi_client'
4
+
5
+ TritonClient = RubyTriton::CloudApiClient
@@ -0,0 +1,1665 @@
1
+ # Copyright (c) 2016, Les Technologies Alesium, Inc. All rights reserved.
2
+ #
3
+ # ruby-triton is a simple low-abstraction layer which communicates with Joyent's
4
+ # Triton container as a service.
5
+ #
6
+ # ruby-triton should be thread-safe, and supports pooling of keep-alive
7
+ # connections to the same server (through HTTPClient). It only relies on the
8
+ # standard library and two pure Ruby libraries, so it should work anywhere.
9
+ #
10
+ # For more information about Triton and general ruby-triton usage, please see
11
+ # README.md.
12
+
13
+
14
+
15
+ require 'openssl'
16
+ require 'net/ssh'
17
+ require 'rest-client'
18
+ require 'base64'
19
+ require 'digest'
20
+ require 'time'
21
+ require 'json'
22
+ require 'cgi'
23
+ require 'uri'
24
+
25
+ require File.expand_path('../version', __FILE__)
26
+
27
+
28
+ module RubyTriton
29
+ class CloudApiClient
30
+ DEFAULT_ATTEMPTS = 3
31
+ DEFAULT_OPEN_TIMEOUT = 20
32
+ DEFAULT_READ_TIMEOUT = 20
33
+ DEFAULT_VERIFY_SSL = OpenSSL::SSL::VERIFY_PEER
34
+
35
+ MAX_LIMIT = 1000
36
+ HTTP_AGENT = "ruby-triton/#{VERSION} (#{RUBY_PLATFORM}; #{OpenSSL::OPENSSL_VERSION}) ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
37
+ HTTP_SIGNATURE = 'Signature keyId="/%s/keys/%s",algorithm="%s",signature="%s"'
38
+
39
+ CLOUDAPI_VERSION = 8
40
+ ## TODO: Add this check
41
+ #CLOUDAPI_PATH_REGEX = Regexp.new('^/[^/]+(?:/?/$|/keys|/users|/roles|/jobs)(?:/|$)')
42
+
43
+ ERROR_CLASSES = [ 'BadRequest', 'InternalError', 'InUseError',
44
+ 'InvalidArgument', 'InvalidCredentials', 'InvalidHeader',
45
+ 'InvalidVersion', 'MissingParameter', 'NotAuthorized',
46
+ 'RequestThrottled', 'RequestTooLarge', 'RequestMoved',
47
+ 'ResourceNotFound', 'UnknownError']
48
+
49
+
50
+
51
+
52
+ # Initialize a MantaClient instance.
53
+ #
54
+ # priv_key_data is data read directly from an SSH private key (i.e. RFC 4716
55
+ # format). The method can also accept several optional args: :connect_timeout,
56
+ # :send_timeout, :receive_timeout, :disable_ssl_verification and :attempts.
57
+ # The timeouts are in seconds, and :attempts determines the default number of
58
+ # attempts each method will make upon receiving recoverable errors.
59
+ #
60
+ # Will throw an exception if given a key whose format it doesn't understand.
61
+ def initialize(host, account, priv_key_data, opts = {})
62
+ raise ArgumentError unless host =~ /^https{0,1}:\/\/.*[^\/]/
63
+ raise ArgumentError unless account.is_a?(String) && account.size > 0
64
+
65
+ @host = host
66
+ @account = account
67
+ @subuser = opts[:subuser] ? opts[:subuser] : nil
68
+
69
+ @attempts = opts[:attempts] || DEFAULT_ATTEMPTS
70
+ raise ArgumentError unless @attempts > 0
71
+
72
+ if priv_key_data =~ /BEGIN RSA/
73
+ @digest = OpenSSL::Digest::SHA1.new
74
+ @digest_name = 'rsa-sha1'
75
+ algorithm = OpenSSL::PKey::RSA
76
+ elsif priv_key_data =~ /BEGIN DSA/
77
+ @digest = OpenSSL::Digest::DSS1.new
78
+ @digest_name = 'dsa-sha1'
79
+ algorithm = OpenSSL::PKey::DSA
80
+ else
81
+ raise UnsupportedKey
82
+ end
83
+
84
+ @priv_key = algorithm.new(priv_key_data)
85
+ @fingerprint = OpenSSL::Digest::MD5.hexdigest(@priv_key.to_blob).
86
+ scan(/../).join(':')
87
+
88
+
89
+ if opts[:verify_ssl] == false
90
+ verify_ssl = OpenSSL::SSL::VERIFY_NONE
91
+ end
92
+ @client = RestClient::Resource.new(@host,
93
+ :open_timeout => opts[:open_timeout] || DEFAULT_OPEN_TIMEOUT,
94
+ :read_timeout => opts[:read_timeout] || DEFAULT_READ_TIMEOUT,
95
+ :verify_ssl => verify_ssl || DEFAULT_VERIFY_SSL
96
+ )
97
+ end
98
+
99
+ ##
100
+ # Account
101
+ ##
102
+
103
+ # You can obtain your account details and update them through CloudAPI,
104
+ # although login cannot be changed, and password can not be retrieved.
105
+ #
106
+ # Retrieves your account details. Instead of providing your login name, you
107
+ # can also provide 'my' (i.e. GET /my).
108
+ def get_account(opts= {})
109
+ c = @client["#{@account}"]
110
+ headers = gen_headers(opts)
111
+ attempt(opts[:attempts]) do
112
+ do_get(c, headers)
113
+ end
114
+ end
115
+
116
+ # Update your account details with the given parameters.
117
+ #
118
+ # ==== Options
119
+ #
120
+ # * +:email+ - String
121
+ # * +:companyName+ - String
122
+ # * +:firstName - String
123
+ # * +:lastName - String
124
+ # * +:address - String
125
+ # * +:postalCode - String
126
+ # * +:city - String
127
+ # * +:state - String
128
+ # * +:country - String
129
+ # * +:phone - String
130
+ # * +:triton_cns_enabled - Boolean
131
+ def update_account(opts= {})
132
+ c = @client["#{@account}"]
133
+ headers = gen_headers(opts)
134
+ attempt(opts[:attempts]) do
135
+ do_post(c, headers, opts)
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Keys
141
+ ##
142
+
143
+ # This part of the API is the means by which you operate on your SSH/signing
144
+ # keys. These keys are needed in order to login to instances over SSH, as
145
+ # well as signing requests to this API.
146
+ #
147
+ # Currently CloudAPI supports uploads of public keys in the OpenSSH format.
148
+ #
149
+ # Note that while it's possible to provide a name attribute for an SSH key
150
+ # in order to use it as a human-friendly alias, this attribute's presence is
151
+ # optional. When it's not provided, the ssh key fingerprint will be used as
152
+ # the name instead.
153
+ #
154
+ # For the following routes, the parameter placeholder :key can be replaced
155
+ # with with either the key's name or its fingerprint. It's strongly
156
+ # recommended to use fingerprint when possible, since the name attribute does
157
+ # not have any uniqueness constraints.
158
+ #
159
+ # Lists all public keys we have on record for the specified account.
160
+ def list_keys(opts= {})
161
+ c = @client["#{@user_path}/keys"]
162
+ headers = gen_headers(opts)
163
+ attempt(opts[:attempts]) do
164
+ do_get(c, headers)
165
+ end
166
+ end
167
+
168
+ # Retrieves the record for an individual key.
169
+ #
170
+ # ==== Attributes
171
+ #
172
+ # * +:key String - Public key in OpenSSH format
173
+ def get_key(key, opts= {})
174
+ raise ArgumentError unless key.is_a? String
175
+ c = @client["#{@user_path}/keys/#{key}"]
176
+ headers = gen_headers(opts)
177
+ attempt(opts[:attempts]) do
178
+ do_get(c, headers)
179
+ end
180
+ end
181
+
182
+ # Uploads a new OpenSSH key to Triton for use in HTTP signing and SSH.
183
+ #
184
+ # ==== Attributes
185
+ #
186
+ # * +:key String - Public key in OpenSSH format
187
+ #
188
+ # ==== Options
189
+ #
190
+ # * +:name - String Name for this key
191
+ def create_key(key, opts= {})
192
+ raise ArgumentError unless key.is_a? String
193
+ c = @client["#{@user_path}/keys"]
194
+ headers = gen_headers(opts)
195
+ opts[:key] = key
196
+ attempt(opts[:attempts]) do
197
+ do_post(c, headers, opts)
198
+ end
199
+ end
200
+
201
+ # Deletes a single SSH key, by name or fingerprint.
202
+ #
203
+ # ==== Attributes
204
+ #
205
+ # * +:key String - Public key in OpenSSH format
206
+ def delete_key(key, opts= {})
207
+ raise ArgumentError unless key.is_a? String
208
+ c = @client["#{@user_path}/keys/#{key}"]
209
+ headers = gen_headers(opts)
210
+ attempt(opts[:attempts]) do
211
+ do_delete(c, headers)
212
+ end
213
+ end
214
+
215
+ ##
216
+ # Users
217
+ ##
218
+ # These are users (also known as sub-users); additional users who are
219
+ # authorized to use the same account, but are subject to the RBAC system.
220
+
221
+ # Returns a list of an account's user objects. These have the same format
222
+ # as the main account object.
223
+ def list_users(opts= {})
224
+ c = @client["#{@account}/users"]
225
+ headers = gen_headers(opts)
226
+ attempt(opts[:attempts]) do
227
+ do_get(c, headers)
228
+ end
229
+ end
230
+
231
+ # Get one user for an account.
232
+ #
233
+ # ==== Attributes
234
+ #
235
+ # * +:user String - User name
236
+ def get_user(user, opts = {})
237
+ raise unless user.is_a? String
238
+ c = @client["#{@account}/users/#{user}"]
239
+ headers = gen_headers(opts)
240
+ attempt(opts[:attempts]) do
241
+ do_get(c, headers)
242
+ end
243
+
244
+ end
245
+
246
+ # Creates a new user under an account.
247
+ #
248
+ # ==== Attributes
249
+ #
250
+ # * +:email String - (Required) Email address
251
+ # * +:login String - (Required) Login
252
+ # * +:password String - (Required) Password
253
+ #
254
+ # ==== Options
255
+ #
256
+ # * +:companyName+ - String
257
+ # * +:firstName - String
258
+ # * +:lastName - String
259
+ # * +:address - String
260
+ # * +:postalCode - String
261
+ # * +:city - String
262
+ # * +:state - String
263
+ # * +:country - String
264
+ # * +:phone - String
265
+ def create_user(email, login, password, opts = {})
266
+ raise ArgumentError unless email.is_a? String
267
+ raise ArgumentError unless login.is_a? String
268
+ raise ArgumentError unless password.is_a? String
269
+
270
+ c = @client["#{@account}/users/#{user}"]
271
+ headers = gen_headers(opts)
272
+ opts[:email] = email
273
+ opts[:login] = login
274
+ opts[:password] = password
275
+ attempt(opts[:attempts]) do
276
+ do_post(c, headers, opts)
277
+ end
278
+
279
+ end
280
+
281
+ # Update a user's modifiable properties.
282
+ # Note: Password changes are not allowed using this endpoint; there is an
283
+ # additional methode (change_user_password) for password changes so it can
284
+ # be selectively allowed/disallowed for users using policies.
285
+ #
286
+ # ==== Attributes
287
+ #
288
+ # *+:id - String
289
+ #
290
+ # ==== Options
291
+ #
292
+ # * +:email - String
293
+ # * +:login - String
294
+ # * +:password - String
295
+ # * +:companyName+ - String
296
+ # * +:firstName - String
297
+ # * +:lastName - String
298
+ # * +:address - String
299
+ # * +:postalCode - String
300
+ # * +:city - String
301
+ # * +:state - String
302
+ # * +:country - String
303
+ # * +:phone - String
304
+ def update_user(id, opts = {})
305
+ raise ArgumentError unless id.is_a? String
306
+ c = @client["#{@account}/users/#{id}"]
307
+ headers = gen_headers(opts)
308
+ attempt(opts[:attempts]) do
309
+ do_post(c, headers, opts)
310
+ end
311
+
312
+ end
313
+
314
+ # This is a separate rule for password changes, so different policies can
315
+ # be used for an user trying to modify other data, or only their own password.
316
+ #
317
+ # ==== Attributes
318
+ #
319
+ # *+:user - String
320
+ # *+:password - String
321
+ # *+:password_confirmation - String
322
+ def change_user_password(user, password, password_confirmation, opts = {})
323
+ raise ArgumentError unless user.is_a? String
324
+ raise ArgumentError unless password.is_a? String
325
+ raise ArgumentError unless password_confirmation.is_a? String
326
+ raise InvalidArgument unless password != password_confirmation
327
+ c = @client["#{@account}/users/#{user}/change_password"]
328
+ headers = gen_headers(opts)
329
+ opts[:password] = password
330
+ opts[:password_confirmation] = password_confirmation
331
+ attempt(opts[:attempts]) do
332
+ do_post(c, headers, opts)
333
+ end
334
+
335
+ end
336
+
337
+ # Remove a user. They will no longer be able to use this API.
338
+ #
339
+ # ==== Attributes
340
+ #
341
+ # *+:user - String
342
+ def delete_user(user, opts = {})
343
+ raise ArgumentError unless user.is_a? String
344
+ c = @client["#{@account}/users/#{user}"]
345
+ headers = gen_headers(opts)
346
+ attempt(opts[:attempts]) do
347
+ do_delete(c, headers)
348
+ end
349
+
350
+ end
351
+
352
+ ##
353
+ # Roles
354
+ ##
355
+ # Roles a sub-users can adopt when attempting to access a resource. See the RBAC section for more details.
356
+
357
+ # Returns an array of account roles.
358
+ def list_roles(opts= {})
359
+ c = @client["#{@account}/roles"]
360
+ headers = gen_headers(opts)
361
+ attempt(opts[:attempts]) do
362
+ do_get(c, headers)
363
+ end
364
+ end
365
+
366
+ # Get an account role (:role) by id or name.
367
+ #
368
+ # ==== Attributes
369
+ #
370
+ # * +:role String - id or name of the role
371
+ def get_role(role, opts = {})
372
+ raise unless role.is_a? String
373
+ c = @client["#{@account}/roles/#{role}"]
374
+ headers = gen_headers(opts)
375
+ attempt(opts[:attempts]) do
376
+ do_get(c, headers)
377
+ end
378
+
379
+ end
380
+
381
+ # Create a new role for your account.
382
+ #
383
+ # ==== Attributes
384
+ #
385
+ # * +:name - String (Required) The role's name
386
+ #
387
+ # ==== Options
388
+ #
389
+ # * +:policies - Array This account's policies to be given to this role
390
+ # * +:members - Array This account's user logins to be added to this role (Optional)
391
+ # * +:default_members - Array This account's user logins to be added to this role and have it enabled by default (Optional)
392
+ def create_role(name, opts = {})
393
+ raise ArgumentError unless name.is_a? String
394
+ c = @client["#{@account}/roles"]
395
+ headers = gen_headers(opts)
396
+ attempt(opts[:attempts]) do
397
+ do_post(c, headers, opts)
398
+ end
399
+
400
+ end
401
+
402
+ # Modifies an account role. Anything but id can be modified.
403
+ #
404
+ # ==== Attributes
405
+ #
406
+ # *+:name - String role name or id
407
+ #
408
+ # ==== Options
409
+ #
410
+ # *+:policies - Array This account's policies to be given to this role (Optional)
411
+ # *+:members - Array This account's user logins to be added to this role (Optional)
412
+ # *+:default_members - Array This account's user logins to be added to this role and have it enabled by default (Optional)
413
+ def update_role(name, opts = {})
414
+ raise ArgumentError unless name.is_a? String
415
+ c = @client["#{@account}/roles/#{name}"]
416
+ headers = gen_headers(opts)
417
+ attempt(opts[:attempts]) do
418
+ do_post(c, headers, opts)
419
+ end
420
+
421
+ end
422
+
423
+ # Remove a role. :role must be the role id (a UUID).
424
+ #
425
+ # ==== Attributes
426
+ #
427
+ # *+:role - String UUID of the role
428
+ def delete_role(role, opts = {})
429
+ raise ArgumentError unless role.is_a? String
430
+ c = @client["#{@account}/roles/#{role}"]
431
+ headers = gen_headers(opts)
432
+ attempt(opts[:attempts]) do
433
+ do_delete(c, headers)
434
+ end
435
+
436
+ end
437
+
438
+ ##
439
+ # Role tags
440
+ ##
441
+
442
+ # Sets the given role tags to the provided resource path. resource_path can
443
+ # be the path to any of the CloudAPI resources described in this document:
444
+ # account, keys, users, roles, policies, user's ssh keys, datacenters,
445
+ # images, packages, instances, analytics, instrumentations, firewall rules
446
+ # and networks.
447
+ # For each of these you can set role tags either for an individual resource
448
+ # or for the whole group.
449
+ #
450
+ # ==== Attributes
451
+ #
452
+ # * +:resource_url - String (Required) The resource path to assign a role tags to.
453
+ # * +:role-tag - Array (Required) Array of tags to be assigned/modified
454
+ def create_role_tags(resource_url, role-tag, opts = {})
455
+ raise ArgumentError unless resource_url.is_a? String
456
+ raise ArgumentError unless role-tag.is_a? Array
457
+ c = @client[resource_url]
458
+ headers = gen_headers(opts)
459
+ opts['role-tag'] = role-tag
460
+ attempt(opts[:attempts]) do
461
+ do_put(c, headers, opts)
462
+ end
463
+
464
+ end
465
+
466
+ ##
467
+ # Policies
468
+ ##
469
+ # Retrieves a list of account policies.
470
+ def list_policies(opts= {})
471
+ c = @client["#{@account}/policies"]
472
+ headers = gen_headers(opts)
473
+ attempt(opts[:attempts]) do
474
+ do_get(c, headers)
475
+ end
476
+ end
477
+
478
+ # Get an account policy (:policy) by id.
479
+ #
480
+ # ==== Attributes
481
+ #
482
+ # * +:policy - String id of the policy
483
+ def get_policy(policy, opts = {})
484
+ raise ArgumentError unless policy.is_a? String
485
+ c = @client["#{@account}/policies/#{policy}"]
486
+ headers = gen_headers(opts)
487
+ attempt(opts[:attempts]) do
488
+ do_get(c, headers)
489
+ end
490
+
491
+ end
492
+
493
+ # Creates a new account policy.
494
+ #
495
+ # ==== Attributes
496
+ #
497
+ # * +:name - String The policy name
498
+ # * +:rules - One or more Aperture sentences to be added to the current policy
499
+ #
500
+ # ==== Options
501
+ #
502
+ # * +:description - String A description for this policy (Optional)
503
+ def create_policy(name, rules, opts = {})
504
+ raise ArgumentError unless name.is_a? String
505
+ raise ArgumentError unless rules.is_a? Array
506
+ c = @client["#{@account}/policies"]
507
+ headers = gen_headers(opts)
508
+ opts[:rules] = rules
509
+ attempt(opts[:attempts]) do
510
+ do_post(c, headers, opts)
511
+ end
512
+
513
+ end
514
+
515
+ # Updates an existing account policy. Everything but id can be modified.
516
+ #
517
+ # ==== Attributes
518
+ #
519
+ # * +:policy - String id of the policy
520
+ #
521
+ # ==== Options
522
+ #
523
+ # * +:name - String The policy name
524
+ # * +:rules - One or more Aperture sentences to be added to the current policy
525
+ # * +:description - String A description for this policy (Optional)
526
+ def update_policy(policy, opts = {})
527
+ raise ArgumentError unless name.is_a? String
528
+ c = @client["#{@account}/policies/#{policy}"]
529
+ headers = gen_headers(opts)
530
+ attempt(opts[:attempts]) do
531
+ do_post(c, headers, opts)
532
+ end
533
+
534
+ end
535
+
536
+ # Delete an RBAC policy. :policy must be the policy id (a UUID).
537
+ #
538
+ # ==== Attributes
539
+ #
540
+ # *+:policy - String UUID of the role
541
+ def delete_policy(policy, opts = {})
542
+ raise ArgumentError unless policy.is_a? String
543
+ c = @client["#{@account}/policies/#{policy}"]
544
+ headers = gen_headers(opts)
545
+ attempt(opts[:attempts]) do
546
+ do_delete(c, headers)
547
+ end
548
+
549
+ end
550
+
551
+ ##
552
+ # User SSH Keys
553
+ ##
554
+ # See account keys for a detailed description. Only difference is the path
555
+ # from where you can access users' keys:
556
+
557
+ # Lists all public keys we have on record for the specified account user.
558
+ # See list_keys.
559
+ def list_user_keys(opts= {})
560
+ raise unless @subuser is not nil
561
+ c = @client["#{@account}/users/#{@subuser}/keys"]
562
+ headers = gen_headers(opts)
563
+ attempt(opts[:attempts]) do
564
+ do_get(c, headers)
565
+ end
566
+ end
567
+
568
+ # Retrieves the given key record either by fingerprint or name. See get_key.
569
+ #
570
+ # ==== Attributes
571
+ #
572
+ # * +:key - String id or fingerprint of key
573
+ def get_user_key(key, opts = {})
574
+ raise InvalidCredentials unless @subuser is not nil
575
+ raise ArgumentError unless key.is_a? String
576
+ c = @client["#{@account}/users/#{@subuser}/keys/#{key}"]
577
+ headers = gen_headers(opts)
578
+ attempt(opts[:attempts]) do
579
+ do_get(c, headers)
580
+ end
581
+
582
+ end
583
+
584
+ # Creates a new key record. See create_key.
585
+ #
586
+ # ==== Attributes
587
+ #
588
+ # * +:key - String Public key in OpenSSH format
589
+ #
590
+ # ==== Options
591
+ #
592
+ # * +:name - String Name for this key
593
+ def create_user_key(key, opts = {})
594
+ raise InvalidCredentials unless @subuser is not nil
595
+ raise ArgumentError unless key.is_a? String
596
+ c = @client["#{@account}/users/#{@subuser}/keys"]
597
+ headers = gen_headers(opts)
598
+ opts[:key] = key
599
+ attempt(opts[:attempts]) do
600
+ do_post(c, headers, opts)
601
+ end
602
+
603
+ end
604
+
605
+ # Removes a key. See get_key.
606
+ #
607
+ # ==== Attributes
608
+ #
609
+ # *+:key - String id or fingerprint of key
610
+ def delete_user_key(key, opts = {})
611
+ raise InvalidCredentials unless @subuser is not nil
612
+ raise ArgumentError unless key.is_a? String
613
+ c = @client["#{@account}/users/#{@subuser}/keys/#{key}"]
614
+ headers = gen_headers(opts)
615
+ attempt(opts[:attempts]) do
616
+ do_delete(c, headers)
617
+ end
618
+
619
+ end
620
+
621
+
622
+ ##
623
+ # Config
624
+ ##
625
+ # These endpoints allow you to get and set configuration values related to
626
+ # your account.
627
+
628
+ # Outputs configuration for your account. The configuration values that are
629
+ # currently configurable are:
630
+ # default_network: the network that docker containers are provisioned on.
631
+ def get_config(opts = {})
632
+ c = @client["#{@account}/config"]
633
+ headers = gen_headers(opts)
634
+ attempt(opts[:attempts]) do
635
+ do_get(c, headers)
636
+ end
637
+
638
+ end
639
+
640
+ # Updates configuration values for your account.
641
+ #
642
+ # ==== Attributes
643
+ #
644
+ # * +:default_network - String ID of the network used for provisioning docker containers
645
+ def update_config(default_network, opts = {})
646
+ raise ArgumentError unless default_network.is_a? String
647
+ c = @client["#{@account}/config"]
648
+ headers = gen_headers(opts)
649
+ opts[:default_network] = default_network
650
+ attempt(opts[:attempts]) do
651
+ do_put(c, headers, opts)
652
+ end
653
+
654
+ end
655
+
656
+ ##
657
+ # datacenters
658
+ ##
659
+
660
+ # Provides a list of all datacenters this cloud is aware of.
661
+ def list_datacenters(opts= {})
662
+ c = @client["#{@account}/datacenters"]
663
+ headers = gen_headers(opts)
664
+ attempt(opts[:attempts]) do
665
+ do_get(c, headers)
666
+ end
667
+ end
668
+
669
+ # Gets an individual datacenter by name. Returns an HTTP redirect to
670
+ # your client, where the datacenter url is in the Location header.
671
+ #
672
+ # ==== Attributes
673
+ #
674
+ # * +:name - String datacenter name
675
+ def get_datacenter(name, opts = {})
676
+ raise ArgumentError unless name.is_a? String
677
+ c = @client["#{@account}/datacenters/#{name}"]
678
+ headers = gen_headers(opts)
679
+ attempt(opts[:attempts]) do
680
+ raise InvalidArgument unless c.is_a? RestClient::Resource
681
+ result = c.get(headers)
682
+ raise InternalError unless result.is_a? RestClient::Response
683
+
684
+ if result.code == 302
685
+ return JSON.parse(result.body)
686
+ end
687
+
688
+ raise_error(result)
689
+ end
690
+
691
+ end
692
+
693
+ ##
694
+ # Services
695
+ ##
696
+
697
+ # Provides the URL endpoints for services for this datacenter. It is a
698
+ # mapping of service name to URL endpoint.
699
+ def list_services(opts= {})
700
+ c = @client["#{@account}/services"]
701
+ headers = gen_headers(opts)
702
+ attempt(opts[:attempts]) do
703
+ do_get(c, headers)
704
+ end
705
+ end
706
+
707
+ ##
708
+ # Images
709
+ ##
710
+ # An image contains the software packages that will be available on
711
+ # newly-provisioned instance. In the case of hardware virtual machines,
712
+ # the image also includes the operating system.
713
+
714
+ # Provides a list of images available in this datacenter.
715
+ # Note: Currently, Docker images are not included in this endpoint's
716
+ # responses. You must use docker images against the docker service for this
717
+ # datacenter.
718
+ #
719
+ # ==== Options
720
+ #
721
+ # * +:name - String The "friendly" name for this image
722
+ # * +:os - String The underlying operating system for this image
723
+ # * +:version - String The version for this image
724
+ # * +:public - Boolean Filter public/private images
725
+ # * +:state - String Filter on image state. By default only active images are shown. Use ?state=all to list all images
726
+ # * +:owner - String Filter on owner UUID
727
+ # * +:type - String Filter on image type. The types changed in v8.0.0
728
+ def list_images(opts= {})
729
+ url = "#{@account}/images"
730
+ if opts.size > 0
731
+ url = url + '?' + URI.encode_www_form(opts)
732
+ end
733
+ c = @client[url]
734
+ headers = gen_headers(opts)
735
+ attempt(opts[:attempts]) do
736
+ do_get(c, headers)
737
+ end
738
+ end
739
+
740
+ # Gets an individual image by id.
741
+ #
742
+ # ==== Attributes
743
+ #
744
+ # * +:image - String id of the image
745
+ def get_image(image, opts = {})
746
+ raise ArgumentError unless image.is_a? String
747
+ c = @client["#{@account}/images/#{image}"]
748
+ headers = gen_headers(opts)
749
+ attempt(opts[:attempts]) do
750
+ do_get(c, headers)
751
+ end
752
+
753
+ end
754
+
755
+ # Delete an image. Caller must be the owner of the image to delete it.
756
+ #
757
+ # ==== Attributes
758
+ #
759
+ # *+:image - String id of the image
760
+ def delete_image(image, opts = {})
761
+ raise ArgumentError unless image.is_a? String
762
+ c = @client["#{@account}/images/#{policy}"]
763
+ headers = gen_headers(opts)
764
+ attempt(opts[:attempts]) do
765
+ do_delete(c, headers)
766
+ end
767
+
768
+ end
769
+
770
+ # Exports an image to the specified Manta path. Caller must be the owner of
771
+ # the image, and the correspondent Manta path prefix, in order to export it.
772
+ # Both the image manifest and the image file will be exported, and their
773
+ # filenames will default to the following format when the specified manta
774
+ # path is a directory
775
+ #
776
+ # ==== Attributes
777
+ #
778
+ # * +:image - String id of the image
779
+ # * +:manta_path - String Manta path prefix used when exporting the image
780
+ def export_image(image, manta_path, opts = {})
781
+ raise ArgumentError unless image.is_a? String
782
+ raise ArgumentError unless manta_path.is_a? String
783
+ c = @client["#{@account}/images/#{image}?action=export"]
784
+ headers = gen_headers(opts)
785
+ opts[:manta_path] = manta_path
786
+ attempt(opts[:attempts]) do
787
+ do_post(c, headers, opts)
788
+ end
789
+
790
+ end
791
+
792
+ # Create a new custom image from an instance. The typical process is:
793
+ #
794
+ # 1. Customize an instance the way you want it.
795
+ # 2. Call this method (create_image_from_machine) to create a new image.
796
+ # 3. Repeat from step 1 if more customizations are desired with different images.
797
+ # 4. Use the new image(s) for provisioning via create_machine.
798
+ #
799
+ # ==== Attributes
800
+ #
801
+ # * +:machine - String The prepared and stopped instance UUID from which the image is to be created
802
+ # * +:name - String The name of the custom image, e.g. "my-image". See the IMGAPI docs for details
803
+ # * +:version - String The version of the custom image, e.g. "1.0.0". See the IMGAPI docs for details
804
+ #
805
+ # ==== Options
806
+ #
807
+ # * +:description - String The image description
808
+ # * +:homepage - String The image homepage
809
+ # * +:eula - String The image eula
810
+ # * +:acl - String The image acl
811
+ # * +:tags - String The image tags
812
+ def create_image_from_machine(machine, name, version, opts = {})
813
+ raise ArgumentError unless machine.is_a? String
814
+ raise ArgumentError unless name.is_a? String
815
+ raise ArgumentError unless version.is_a? String
816
+ c = @client["#{@account}/images"]
817
+ headers = gen_headers(opts)
818
+ opts[:machine] = machine
819
+ opts[:name] = name
820
+ opts[:version] = version
821
+ attempt(opts[:attempts]) do
822
+ do_post(c, headers, opts)
823
+ end
824
+
825
+ end
826
+
827
+ # Updates metadata about an image.
828
+ #
829
+ # ==== Attributes
830
+ #
831
+ # * +:machine - String The prepared and stopped instance UUID from which the image is to be created
832
+ #
833
+ # ==== Options
834
+ #
835
+ # * +:name - String The name of the custom image, e.g. "my-image". See the IMGAPI docs for details
836
+ # * +:version - String The version of the custom image, e.g. "1.0.0". See the IMGAPI docs for details
837
+ # * +:description - String The image description
838
+ # * +:homepage - String The image homepage
839
+ # * +:eula - String The image eula
840
+ # * +:acl - String The image acl
841
+ # * +:tags - String The image tags
842
+ def update_image(machine, opts = {})
843
+ raise ArgumentError unless machine.is_a? String
844
+ c = @client["#{@account}/images/#{image}?action=update"]
845
+ headers = gen_headers(opts)
846
+ attempt(opts[:attempts]) do
847
+ do_post(c, headers, opts)
848
+ end
849
+
850
+ end
851
+
852
+ ##
853
+ # Packages
854
+ ##
855
+ # Packages are named collections of resources that are used to describe the
856
+ # dimensions of either a container or a hardware virtual machine. These
857
+ # resources include (but are not limited to) RAM size, CPUs, CPU caps,
858
+ # lightweight threads, disk space, swap size, and logical networks.
859
+
860
+ # Provides a list of packages available in this datacenter.
861
+ #
862
+ # ==== Options
863
+ #
864
+ # * +:name - String The "friendly" name for this package
865
+ # * +:memory - Number How much memory will by available (in MiB)
866
+ # * +:disk - Number How much disk space will be available (in MiB)
867
+ # * +:swap - Number How much swap space will be available (in MiB)
868
+ # * +:lwps - Number Maximum number of light-weight processes (threads) allowed
869
+ # * +:vcpus - Number Number of vCPUs for this package
870
+ # * +:version - String The version of this package
871
+ # * +:group - String The group this package belongs to
872
+ def list_packages(opts= {})
873
+ url = "#{@account}/packages"
874
+ if opts.size > 0
875
+ url = url + '?' + URI.encode_www_form(opts)
876
+ end
877
+ c = @client[url]
878
+ headers = gen_headers(opts)
879
+ attempt(opts[:attempts]) do
880
+ do_get(c, headers)
881
+ end
882
+ end
883
+
884
+ # Gets an individual image by id.
885
+ #
886
+ # ==== Attributes
887
+ #
888
+ # * +:package - String id of the package
889
+ def get_package(package, opts = {})
890
+ raise ArgumentError unless package.is_a? String
891
+ c = @client["#{@account}/packages/#{package}"]
892
+ headers = gen_headers(opts)
893
+ attempt(opts[:attempts]) do
894
+ do_get(c, headers)
895
+ end
896
+
897
+ end
898
+
899
+ ##
900
+ # Instances
901
+ ##
902
+ # Triton supports three different types of instances:
903
+ #
904
+ # Docker containers. OS-virtualized instances managed through the Docker client.
905
+ # Infrastructure containers. More traditional OS-virtualized instances
906
+ # running SmartOS or more Linux distributions.
907
+ # Hardware-virtualized machines. Hardware-virtualized instances (KVM) for
908
+ # running legacy or special-purpose operating systems.
909
+ #
910
+ # Infrastructure and Docker containers are lightweight, offering the most
911
+ # performance, observability and operational flexibility. Harware-virtualized
912
+ # machines are useful for non-SmartOS or non-Linux stacks.
913
+
914
+ # Lists all instances we have on record for your account. If you have a large
915
+ # number of instances, you can filter using the input parameters listed below.
916
+ # Note that deleted instances are returned only if the instance history has
917
+ # not been purged from Triton.
918
+ #
919
+ # You can paginate this API by passing in offset and limit. HTTP responses
920
+ # will contain the additional headers x-resource-count and x-query-limit.
921
+ # If x-resource-count is less than x-query-limit, you're done, otherwise call
922
+ # the API again with offset set to offset + limit to fetch additional instances.
923
+ #
924
+ # Note that there is a opts[:method] = :head form of this API, so you can
925
+ # retrieve the number of instances without retrieving a JSON describing the
926
+ # instances themselves.
927
+ #
928
+ # ==== Options
929
+ # *+:type - String (deprecated) The type of instance (virtualmachine or smartmachine)
930
+ # *+:brand - String (v8.0+) The type of instance (e.g. lx)
931
+ # *+:name - String Machine name to find (will make your list size 1, or 0 if nothing found)
932
+ # *+:image - String Image id; returns instances provisioned with that image
933
+ # *+:state - String The current state of the instance (e.g. running)
934
+ # *+:memory - Integer The current size of the RAM deployed for the instance (in MiB)
935
+ # *+:tombstone - Boolean Include destroyed and failed instances available in instance history
936
+ # *+:limit - Number Return a max of N instances; default is 1000 (which is also the maximum allowable result set size)
937
+ # *+:offset - Number Get a limit number of instances starting at this offset
938
+ # *+:tag.$name - String An arbitrary set of tags can be used for querying, assuming they are prefixed with "tag."
939
+ # *+:docker - Boolean Whether to only list Docker instances, or only non-Docker instances, if present. Defaults to showing all instances.
940
+ # *+:credentials -Boolean Whether to include the generated credentials for instances, if present. Defaults to false
941
+ def list_machines(opts= {})
942
+ url = "#{@account}/machines"
943
+ # TODO fix for head query
944
+ opts[:limit] = opts[:limit] ? MAX_LIMIT
945
+ raise ArgumentError unless 0 < opts[:limit] && opts[:limit] <= MAX_LIMIT
946
+ if opts.size > 0
947
+ url = url + '?' + URI.encode_www_form(opts)
948
+ end
949
+ c = @client[url]
950
+ headers = gen_headers(opts)
951
+ attempt(opts[:attempts]) do
952
+ if opts[:head]
953
+ do_head(c, headers)
954
+ else
955
+ do_get(c, headers)
956
+ end
957
+ end
958
+ end
959
+
960
+
961
+ # Gets the details for an individual instance.
962
+ #
963
+ # Deleted instances are returned only if the instance history has not been
964
+ # purged from Triton.
965
+ #
966
+ # ==== Attributes
967
+ #
968
+ # * +:instance - String id of the instance
969
+ def get_instance(instance, opts = {})
970
+ raise ArgumentError unless instance.is_a? String
971
+ c = @client["#{@account}/instances/#{instance}"]
972
+ headers = gen_headers(opts)
973
+ attempt(opts[:attempts]) do
974
+ do_get(c, headers)
975
+ end
976
+
977
+ end
978
+
979
+ # Allows you to provision an instance.
980
+ # If you do not specify a name, CloudAPI will generate a random one for you.
981
+ # If you have enabled Triton CNS on your account, this name will also be
982
+ # used in DNS to refer to the new instance (and must therefore consist of
983
+ # DNS-safe characters only).
984
+ #
985
+ # Your instance will initially be not available for login (Triton must
986
+ # provision and boot it); you can poll GetMachine for its status. When the
987
+ # state field is equal to running, you can log in. If the instance is a
988
+ # brand other than kvm, you can usually use any of the SSH keys managed
989
+ # under the keys section of CloudAPI to login as any POSIX user on the OS.
990
+ # You can add/remove keys over time, and the instance will automatically
991
+ # work with that set.
992
+ #
993
+ # If the the instance has a brand kvm, and of a UNIX-derived OS (e.g. Linux),
994
+ # you must have keys uploaded before provisioning; that entire set of keys
995
+ # will be written out to /root/.ssh/authorized_keys in the new instance,
996
+ # and you can SSH in using one of those keys. Changing the keys over time
997
+ # under your account will not affect a running hardware virtual machine in
998
+ # any way; those keys are statically written at provisioning-time only, and
999
+ # you will need to manually manage them on the instance itself.
1000
+ #
1001
+ # ==== Attributes
1002
+ #
1003
+ # * +:image - String id of the image
1004
+ # * +:package - String id of the packge
1005
+ #
1006
+ # ==== Options
1007
+ #
1008
+ # * +:name - String Friendly name for this instance; default is the first 8 characters of the machine id
1009
+ # * +:networks - Array Desired networks ids, obtained from list_networks
1010
+ # * +:locality - Object[String => Array] Optionally specify which instances the new instance should be near or far from
1011
+ # * +:metadata.$name - String An arbitrary set of metadata key/value pairs can be set at provision time, but they must be prefixed with "metadata."
1012
+ # * +:tag.$name - String An arbitrary set of tags can be set at provision time, but they must be prefixed with "tag."
1013
+ # * +:firewall_enabled - Boolean Completely enable or disable firewall for this instance. Default is false
1014
+
1015
+ def create_machine(image, package, opts= {})
1016
+ raise ArgumentError unless image.is_a? String
1017
+ raise ArgumentError unless package.is_a? String
1018
+ c = @client["#{@account}/machines"]
1019
+ headers = gen_headers(opts)
1020
+ opts[:image] = machine
1021
+ opts[:package] = name
1022
+ attempt(opts[:attempts]) do
1023
+ do_post(c, headers, opts)
1024
+ end
1025
+
1026
+ end
1027
+
1028
+ # Allows you to shut down an instance. POST to the instance name with an
1029
+ # action of stop.
1030
+ #
1031
+ # You can poll on get_machine until the state is stopped.
1032
+ # ==== Attributes
1033
+ #
1034
+ # * +:machine - String id of the machine
1035
+ def stop_machine(machine, opts= {})
1036
+ raise ArgumentError unless machine.is_a? String
1037
+ c = @client["#{@account}/machines/#{machine}?action=stop"]
1038
+ attempt(opts[:attempts]) do
1039
+ do_post(c, headers, opts)
1040
+ end
1041
+
1042
+ end
1043
+
1044
+ # Allows you to boot up an instance. POST to the instance name with an
1045
+ # action of start.
1046
+ #
1047
+ # You can poll on get_machine until the state is running.
1048
+ # ==== Attributes
1049
+ #
1050
+ # * +:machine - String id of the machine
1051
+ def start_machine(machine, opts= {})
1052
+ raise ArgumentError unless machine.is_a? String
1053
+ c = @client["#{@account}/machines/#{machine}?action=start"]
1054
+ attempt(opts[:attempts]) do
1055
+ do_post(c, headers, opts)
1056
+ end
1057
+
1058
+ end
1059
+
1060
+ # Allows you to reboot an instance. POST to the instance name with an
1061
+ # action of reboot.
1062
+ #
1063
+ # You can poll on get_machine until the state is running.
1064
+ # ==== Attributes
1065
+ #
1066
+ # * +:machine - String id of the machine
1067
+ def reboot_machine(machine, opts= {})
1068
+ raise ArgumentError unless machine.is_a? String
1069
+ c = @client["#{@account}/machines/#{machine}?action=reboot"]
1070
+ attempt(opts[:attempts]) do
1071
+ do_post(c, headers, opts)
1072
+ end
1073
+
1074
+ end
1075
+
1076
+
1077
+ # Resize an instance to a new package (a.k.a. instance type).
1078
+ #
1079
+ # Resizing is only supported for containers (instances which are not
1080
+ # hardware virtual machines -- they have brand=kvm). Hardware virtual machines
1081
+ # cannot be resized.
1082
+ #
1083
+ # Resizing is not guaranteed to work, especially when resizing upwards in
1084
+ # resources. It is best-effort, and may fail. Resizing downwards will usually
1085
+ # succeed.
1086
+ # ==== Attributes
1087
+ #
1088
+ # * +:machine - String id of the machine
1089
+ # * +:package - String A package id, as returned from list_packages
1090
+ def resize_machine(machine, package, opts= {})
1091
+ raise ArgumentError unless machine.is_a? String
1092
+ raise ArgumentError unless package.is_a? String
1093
+ c = @client["#{@account}/machines/#{machine}?action=resize"]
1094
+ opts['package'] = package
1095
+ attempt(opts[:attempts]) do
1096
+ do_post(c, headers, opts)
1097
+ end
1098
+
1099
+ end
1100
+
1101
+ # Allows you to rename an instance. POST to the instance id with an action
1102
+ # of rename. You must additionally include a new name for the instance.
1103
+ #
1104
+ # ==== Attributes
1105
+ #
1106
+ # * +:machine - String id of the machine
1107
+ # * +:name - String The new "friendly" name for this instance
1108
+ def rename_machine(machine, name, opts= {})
1109
+ raise ArgumentError unless machine.is_a? String
1110
+ raise ArgumentError unless name.is_a? String
1111
+ c = @client["#{@account}/machines/#{machine}?action=rename"]
1112
+ opts['name'] = name
1113
+ attempt(opts[:attempts]) do
1114
+ do_post(c, headers, opts)
1115
+ end
1116
+
1117
+ end
1118
+
1119
+ # Allows you to enable the firewall for an instance.
1120
+ #
1121
+ # ==== Attributes
1122
+ #
1123
+ # * +:machine - String id of the machine
1124
+ def enable_machine_firewall(machine, opts= {})
1125
+ raise ArgumentError unless machine.is_a? String
1126
+ c = @client["#{@account}/machines/#{machine}?action=enable_firewall"]
1127
+ attempt(opts[:attempts]) do
1128
+ do_post(c, headers, opts)
1129
+ end
1130
+
1131
+ end
1132
+
1133
+ # Allows you to completely disable the firewall of an instance.
1134
+ #
1135
+ # ==== Attributes
1136
+ #
1137
+ # * +:machine - String id of the machine
1138
+ def disable_machine_firewall(machine, opts= {})
1139
+ raise ArgumentError unless machine.is_a? String
1140
+ c = @client["#{@account}/machines/#{machine}?action=disable_firewall"]
1141
+ attempt(opts[:attempts]) do
1142
+ do_post(c, headers, opts)
1143
+ end
1144
+
1145
+ end
1146
+
1147
+ # Allows you to take a snapshot of an instance. Once you have one or more
1148
+ # snapshots, you can boot the instance from a previous snapshot.
1149
+ #
1150
+ # Snapshots are not usable with other instances; they are a point-in-time
1151
+ # snapshot of the current instance. Snapshots can also only be taken of
1152
+ # instances that are not of brand 'kvm'.
1153
+ #
1154
+ # Since instance instances use a copy-on-write filesystem, snapshots take up
1155
+ # increasing amounts of space as the filesystem changes over time. There is
1156
+ # a limit to how much space snapshots are allowed to take. Plan your
1157
+ # snapshots accordingly.
1158
+ #
1159
+ # You can poll on get_machine_snapshot until the state is created.
1160
+ #
1161
+ # ==== Attributes
1162
+ #
1163
+ # * +:machine - String id of the machine
1164
+ #
1165
+ # ==== Options
1166
+ #
1167
+ # * +:name - String The name to assign to the new snapshot
1168
+ def create_machine_snapshot(machine, opts= {})
1169
+ raise ArgumentError unless machine.is_a? String
1170
+ c = @client["#{@account}/machines/#{machine}/snapshots"]
1171
+ attempt(opts[:attempts]) do
1172
+ do_post(c, headers, opts)
1173
+ end
1174
+
1175
+ end
1176
+
1177
+ # If an instance is in the 'stopped' state, you can choose to start the
1178
+ # instance from the referenced snapshot. This is effectively a means to
1179
+ # roll back instance state.
1180
+ #
1181
+ # ==== Attributes
1182
+ #
1183
+ # * +:machine - String id of the machine
1184
+ # * +:snapshot - String The name of the snapshot
1185
+ #
1186
+ # ==== Options
1187
+ #
1188
+ # * +:name - String The name to assign to the new snapshot
1189
+ def start_machine_from_snapshot(machine, snapshot, opts= {})
1190
+ raise ArgumentError unless machine.is_a? String
1191
+ raise ArgumentError unless snapshot.is_a? String
1192
+ c = @client["#{@account}/machines/#{machine}/snapshots/#{snapshot}"]
1193
+ attempt(opts[:attempts]) do
1194
+ do_post(c, headers, opts)
1195
+ end
1196
+
1197
+ end
1198
+
1199
+ # Lists all snapshots taken for a given instance. There are no filtration
1200
+ # parameters for this API.
1201
+ #
1202
+ # ==== Attributes
1203
+ #
1204
+ # * +:machine - String id of the machine
1205
+ def list_machine_snapshots(machine, opts= {})
1206
+ raise ArgumentError unless machine.is_a? String
1207
+ c = @client["#{@account}/machines/#{machine}/snapshots"]
1208
+ attempt(opts[:attempts]) do
1209
+ do_get(c, headers)
1210
+ end
1211
+
1212
+ end
1213
+
1214
+ # Gets the state of the named snapshot.
1215
+ #
1216
+ # ==== Attributes
1217
+ #
1218
+ # * +:machine - String id of the machine
1219
+ # * +:snapshot - String The name of the snapshot
1220
+ def get_machine_snapshots(machine, snapshot, opts= {})
1221
+ raise ArgumentError unless machine.is_a? String
1222
+ raise ArgumentError unless snapshot.is_a? String
1223
+ c = @client["#{@account}/machines/#{machine}/snapshots/#{snapshot}"]
1224
+ attempt(opts[:attempts]) do
1225
+ do_get(c, headers)
1226
+ end
1227
+
1228
+ end
1229
+
1230
+ # Deletes the specified snapshot of an instance.
1231
+ #
1232
+ # ==== Attributes
1233
+ #
1234
+ # * +:machine - String id of the machine
1235
+ # * +:snapshot - String The name of the snapshot
1236
+ def delete_machine_snapshot(machine, snapshot, opts= {})
1237
+ raise ArgumentError unless machine.is_a? String
1238
+ raise ArgumentError unless snapshot.is_a? String
1239
+ c = @client["#{@account}/machines/#{machine}/snapshots/#{snapshot}"]
1240
+ attempt(opts[:attempts]) do
1241
+ do_delete(c, headers)
1242
+ end
1243
+
1244
+ end
1245
+
1246
+ # Allows you to update the metadata for a given instance. Note that updating
1247
+ # the metadata via CloudAPI will result in the metadata being updated in the
1248
+ # running instance.
1249
+ #
1250
+ # The semantics of this call are subtly different that the add_machine_tags
1251
+ # call -- any metadata keys passed in here are created if they do not exist,
1252
+ # and overwritten if they do.
1253
+ #
1254
+ # ==== Attributes
1255
+ #
1256
+ # * +:machine - String id of the machine
1257
+ # * +:keys - String or Json object of keys to update
1258
+ #
1259
+ # ==== Options
1260
+ #
1261
+ # * +:name - String The name to assign to the new snapshot
1262
+ def update_machine_metadata(machine, keys, opts= {})
1263
+ raise ArgumentError unless machine.is_a? String
1264
+ raise ArgumentError unless keys.is_a? String || keys.is_a? JSON
1265
+ c = @client["#{@account}/machines/#{machine}/snapshots/#{snapshot}"]
1266
+ opts['keys'] = keys
1267
+ attempt(opts[:attempts]) do
1268
+ do_post(c, headers, opts)
1269
+ end
1270
+
1271
+ end
1272
+
1273
+ # Returns the complete set of metadata associated with this instance.
1274
+ #
1275
+ # ==== Attributes
1276
+ #
1277
+ # * +:machine - String id of the machine
1278
+ #
1279
+ # ==== Options
1280
+ #
1281
+ # * +:credentials - Boolean Whether or not to return instance credentials. Defaults to false
1282
+ def list_machine_metadata(machine, opts= {})
1283
+ raise ArgumentError unless machine.is_a? String
1284
+ url = "#{@account}/machines/#{machine}/metadata"
1285
+ if opts.size > 0 && opts['credentials'].is_a? Boolean
1286
+ if opts['credentials']
1287
+ url = url + '?' + "credentials=true"
1288
+ end
1289
+ end
1290
+ c = @client[url]
1291
+ attempt(opts[:attempts]) do
1292
+ do_get(c, headers)
1293
+ end
1294
+
1295
+ end
1296
+
1297
+
1298
+ # Returns a single metadata entry associated with this instance.
1299
+ #
1300
+ # ==== Attributes
1301
+ #
1302
+ # * +:machine - String id of the machine
1303
+ # * +:key - String Name of metadata value to retrieve
1304
+ def get_machine_metadata(machine, key,opts= {})
1305
+ raise ArgumentError unless machine.is_a? String
1306
+ raise ArgumentError unless key.is_a? String
1307
+ c = @client["#{@account}/machines/#{machine}/metadata/#{key}"]
1308
+ attempt(opts[:attempts]) do
1309
+ do_get(c, headers)
1310
+ end
1311
+
1312
+ end
1313
+
1314
+ # Deletes a single metadata key from this instance.
1315
+ #
1316
+ # ==== Attributes
1317
+ #
1318
+ # * +:machine - String id of the machine
1319
+ # * +:key - String Name of metadata value to delete
1320
+ def delete_machine_metadata(machine, key,opts= {})
1321
+ raise ArgumentError unless machine.is_a? String
1322
+ raise ArgumentError unless key.is_a? String
1323
+ c = @client["#{@account}/machines/#{machine}/metadata/#{key}"]
1324
+ attempt(opts[:attempts]) do
1325
+ do_delete(c, headers)
1326
+ end
1327
+
1328
+ end
1329
+
1330
+ # Deletes all metadata keys from this instance.
1331
+ #
1332
+ # ==== Attributes
1333
+ #
1334
+ # * +:machine - String id of the machine
1335
+ def delete_all_machine_metadata(machine, opts= {})
1336
+ raise ArgumentError unless machine.is_a? String
1337
+ c = @client["#{@account}/machines/#{machine}/metadata"]
1338
+ attempt(opts[:attempts]) do
1339
+ do_delete(c, headers)
1340
+ end
1341
+
1342
+ end
1343
+
1344
+ # Set tags on the given instance. A pre-existing tag with the same name as
1345
+ # one given will be overwritten.
1346
+ # Note: This action is asynchronous. You can poll on ListMachineTags to wait
1347
+ # for the update to be complete (the triton instance tag set -w,--wait
1348
+ # option does this).
1349
+ #
1350
+ # ==== Attributes
1351
+ #
1352
+ # * +:machine - String id of the machine
1353
+ # * +:tags - Array tags to be added
1354
+ def add_machine_tags(machine, tags, opts= {})
1355
+ raise ArgumentError unless machine.is_a? String
1356
+ raise ArgumentError unless tags.is_a? Hash
1357
+ c = @client["#{@account}/machines/#{machine}/tags"]
1358
+ opts[:tags] = tags
1359
+ attempt(opts[:attempts]) do
1360
+ do_post(c, headers, opts)
1361
+ end
1362
+
1363
+ end
1364
+
1365
+ # Fully replace all tags on an instance with the given tags.
1366
+ # Note: This action is asynchronous. You can poll on ListMachineTags to wait
1367
+ # for the update to be complete (the triton instance tag set -w,--wait
1368
+ # option does this).
1369
+ #
1370
+ # ==== Attributes
1371
+ #
1372
+ # * +:machine - String id of the machine
1373
+ # * +:tags - Array tags to replace
1374
+ def replace_machine_tags(machine, tags, opts= {})
1375
+ raise ArgumentError unless machine.is_a? String
1376
+ raise ArgumentError unless tags.is_a? Hash
1377
+ c = @client["#{@account}/machines/#{machine}/tags"]
1378
+ opts[:tags] = tags
1379
+ attempt(opts[:attempts]) do
1380
+ do_put(c, headers, opts)
1381
+ end
1382
+
1383
+ end
1384
+
1385
+ # Fully replace all tags on an instance with the given tags.
1386
+ # Note: This action is asynchronous. You can poll on ListMachineTags to wait
1387
+ # for the update to be complete (the triton instance tag set -w,--wait
1388
+ # option does this).
1389
+ #
1390
+ # ==== Attributes
1391
+ #
1392
+ # * +:machine - String id of the machine
1393
+ def list_machine_tags(machine, opts= {})
1394
+ raise ArgumentError unless machine.is_a? String
1395
+ c = @client["#{@account}/machines/#{machine}/tags"]
1396
+ attempt(opts[:attempts]) do
1397
+ do_get(c, headers)
1398
+ end
1399
+
1400
+ end
1401
+
1402
+ # Returns the value for a single tag on this instance.
1403
+ #
1404
+ # Typically one calls CloudAPI endpoints with Accept: application/json.
1405
+ # This endpoint can be called that way, or alternatively with Accept:
1406
+ # text/plain to get the non-JSON value in the response.
1407
+ #
1408
+ # ==== Attributes
1409
+ #
1410
+ # * +:machine - String id of the machine
1411
+ # * +:tag - String tag to get
1412
+ def get_machine_tags(machine, tag, opts= {})
1413
+ raise ArgumentError unless machine.is_a? String
1414
+ raise ArgumentError unless tag.is_a? String
1415
+ c = @client["#{@account}/machines/#{machine}/tags/#{tag}"]
1416
+ attempt(opts[:attempts]) do
1417
+ do_get(c, headers)
1418
+ end
1419
+
1420
+ end
1421
+
1422
+ # Deletes a single tag from this instance.
1423
+ #
1424
+ # Note: This action is asynchronous. You can poll on ListMachineTags to wait
1425
+ # for the update to be complete (the triton instance tag delete -w,--wait
1426
+ # option does this).
1427
+ #
1428
+ # ==== Attributes
1429
+ #
1430
+ # * +:machine - String id of the machine
1431
+ # * +:tag - String tag to get
1432
+ def delete_machine_tag(machine, tag, opts= {})
1433
+ raise ArgumentError unless machine.is_a? String
1434
+ raise ArgumentError unless tag.is_a? String
1435
+ c = @client["#{@account}/machines/#{machine}/tags/#{tag}"]
1436
+ attempt(opts[:attempts]) do
1437
+ do_delete(c, headers)
1438
+ end
1439
+
1440
+ end
1441
+
1442
+ # Deletes all tags from an instance.
1443
+ #
1444
+ # Note: This action is asynchronous. You can poll on ListMachineTags to wait
1445
+ # for the update to be complete (the triton instance tag delete -w,--wait
1446
+ # option does this).
1447
+ #
1448
+ # ==== Attributes
1449
+ #
1450
+ # * +:machine - String id of the machine
1451
+ def delete_machine_tags(machine, opts= {})
1452
+ raise ArgumentError unless machine.is_a? String
1453
+ c = @client["#{@account}/machines/#{machine}/tags"]
1454
+ attempt(opts[:attempts]) do
1455
+ do_delete(c, headers)
1456
+ end
1457
+
1458
+ end
1459
+
1460
+ # Allows you to completely destroy an instance.
1461
+ #
1462
+ # ==== Attributes
1463
+ #
1464
+ # * +:machine - String id of the machine
1465
+ def delete_machine(machine, opts= {})
1466
+ raise ArgumentError unless machine.is_a? String
1467
+ c = @client["#{@account}/machines/#{machine}"]
1468
+ attempt(opts[:attempts]) do
1469
+ do_delete(c, headers)
1470
+ end
1471
+
1472
+ end
1473
+
1474
+ # Provides a list of an instance's accomplished actions. Results are sorted
1475
+ # from newest to oldest action.
1476
+ #
1477
+ # Note that the complete audit trail is returned only if the instance history
1478
+ # and job records have not been purged from Triton.
1479
+ #
1480
+ # ==== Attributes
1481
+ #
1482
+ # * +:machine - String id of the machine
1483
+ def machine_audit(machine, opts= {})
1484
+ raise ArgumentError unless machine.is_a? String
1485
+ c = @client["#{@account}/machines/#{machine}/audit"]
1486
+ attempt(opts[:attempts]) do
1487
+ do_get(c, headers)
1488
+ end
1489
+
1490
+ end
1491
+
1492
+
1493
+ # ---------------------------------------------------------------------------
1494
+ protected
1495
+
1496
+ # Executes a block. If there is a connection- or corruption-related exception
1497
+ # the block will be reexecuted up to the `tries' argument. It will sleep
1498
+ # for an exponentially-increasing number of seconds between retries.
1499
+ def attempt(tries, &blk)
1500
+ if tries
1501
+ raise ArgumentError unless tries > 0
1502
+ else
1503
+ tries ||= @attempts
1504
+ end
1505
+
1506
+ attempt = 1
1507
+
1508
+ while true
1509
+ begin
1510
+ return yield blk
1511
+ rescue Errno::ECONNREFUSED, RestClient::ServerBrokeConnection
1512
+ raise e if attempt == tries
1513
+ sleep 2 ** attempt
1514
+ attempt += 1
1515
+ end
1516
+ end
1517
+ end
1518
+
1519
+ # Creates a qualified user path consisting of the user and subuser if the
1520
+ # subuser is present. Otherwise, it returns the user
1521
+ def user_path
1522
+ @subuser ? "#{@account}/#{@subuser}" : @account
1523
+ end
1524
+
1525
+ # :m_some_header becomes "M-Some-Header"
1526
+ def symbol_to_header(header_symbol)
1527
+ header_symbol.to_s.split('_').map(&:capitalize).join('-')
1528
+ end
1529
+
1530
+ # Creates headers to be given to the HTTP client and sent to the Manta
1531
+ # service. The most important is the Authorization header, without which
1532
+ # none of this class would work.
1533
+ def gen_headers(opts)
1534
+ now = Time.now.httpdate
1535
+ sig = gen_signature('date: ' + now)
1536
+
1537
+ headers = { "Date" => now,
1538
+ "Authorization" => sig,
1539
+ "User-Agent" => HTTP_AGENT,
1540
+ "Api-Version" => "~#{CLOUDAPI_VERSION}"
1541
+ }
1542
+
1543
+ return headers
1544
+ end
1545
+
1546
+ # Given a chunk of data, creates an HTTP signature which the Manta service
1547
+ # understands and uses for authentication.
1548
+ def gen_signature(data)
1549
+ raise ArgumentError unless data
1550
+
1551
+ sig = @priv_key.sign(@digest, data)
1552
+ base64sig = Base64.strict_encode64(sig)
1553
+
1554
+ return HTTP_SIGNATURE % [user_path, @fingerprint, @digest_name, base64sig]
1555
+ end
1556
+
1557
+ # Returns a full URL for a given path to an object.
1558
+ def cloudapi_url(path)
1559
+ ## TODO: Add this check
1560
+ #raise ArgumentError unless path =~ CLOUDAPI_PATH_REGEX
1561
+ URI.encode(path)
1562
+ end
1563
+
1564
+ # Raises an appropriate exception given the HTTP response. If a 40* is
1565
+ # returned, attempts to look up an appropriate error class and raise,
1566
+ # otherwise raises an UnknownError.
1567
+ def raise_error(result)
1568
+ raise unless result.is_a? RestClient::Response
1569
+
1570
+ err = JSON.parse(result.body)
1571
+ klass = CloudApiClient.const_get err['code']
1572
+ raise klass, err['message']
1573
+ rescue NameError, TypeError, JSON::ParserError
1574
+ raise UnknownError, result.status.to_s + ': ' + result.body
1575
+ end
1576
+
1577
+
1578
+ #
1579
+ # do_get abstraction method to GET request
1580
+ #
1581
+ def do_get(c, headers)
1582
+ raise unless c.is_a? RestClient::Resource
1583
+ result = c.get(headers)
1584
+ raise unless result.is_a? RestClient::Response
1585
+
1586
+ if result.code == 200
1587
+ return JSON.parse(result.body)
1588
+ end
1589
+
1590
+ raise_error(result)
1591
+ end
1592
+
1593
+ #
1594
+ # do_post abstraction method to POST request
1595
+ #
1596
+ def do_post(c, headers, payload)
1597
+ raise unless c.is_a? RestClient::Resource
1598
+ result = c.post(payload.except!(:attempts), headers)
1599
+ raise unless result.is_a? RestClient::Response
1600
+
1601
+ if result.code == 200
1602
+ return JSON.parse(result.body)
1603
+ end
1604
+
1605
+ raise_error(result)
1606
+ end
1607
+
1608
+ #
1609
+ # do_put abstraction method to PUT request
1610
+ #
1611
+ def do_put(c, headers, payload)
1612
+ raise unless c.is_a? RestClient::Resource
1613
+ result = c.put(payload.except!(:attempts), headers)
1614
+ raise unless result.is_a? RestClient::Response
1615
+
1616
+ if result.code == 200
1617
+ return JSON.parse(result.body)
1618
+ end
1619
+
1620
+ raise_error(result)
1621
+ end
1622
+ #
1623
+ # do_delete abstraction method to delete request
1624
+ #
1625
+ def do_delete(c, headers)
1626
+ raise unless c.is_a? RestClient::Resource
1627
+ result = c.delete(headers)
1628
+ raise unless result.is_a? RestClient::Response
1629
+
1630
+ if result.code == 204
1631
+ return true
1632
+ end
1633
+
1634
+ raise_error(result)
1635
+ end
1636
+
1637
+ #
1638
+ # do_head abstraction method to head request
1639
+ #
1640
+ def do_head(c, headers)
1641
+ raise unless c.is_a? RestClient::Resource
1642
+ result = c.head(headers)
1643
+ raise unless result.is_a? RestClient::Response
1644
+
1645
+ if result.code == 200
1646
+ return JSON.parse(result.body)
1647
+ end
1648
+
1649
+ raise_error(result)
1650
+ end
1651
+ #
1652
+ # Validate input parameters
1653
+ # TODO: Not used yet.
1654
+ #
1655
+ def validate_parameters(query_parameters, valid_parameters, opts)
1656
+ raise unless query_parameters.is_a? Hash
1657
+ raise unless valid_parameters.is_a? Hash
1658
+ raise unless opts.is_a? Hash
1659
+ opts.each do | key, val |
1660
+ puts "key = #{key};"
1661
+ end
1662
+ end
1663
+
1664
+ end
1665
+ end