ruby-triton 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +21 -0
- data/example.rb +17 -0
- data/lib/ruby-triton.rb +5 -0
- data/lib/ruby-triton/cloudapi_client.rb +1665 -0
- data/lib/ruby-triton/version.rb +3 -0
- data/ruby-triton.gemspec +31 -0
- data/test/unit/triton_client_test.rb +0 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/example.rb
ADDED
@@ -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
|
data/lib/ruby-triton.rb
ADDED
@@ -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
|