right_api_helper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,357 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ # This contains a bunch or random helper shims
19
+ # Probably should be broken out into separate classes
20
+ #
21
+ module RightApiHelper
22
+ class API15 < Base
23
+
24
+ # If the cloud reports ssh keys, then we assume it requires them to launch
25
+ # servers.
26
+ def requires_ssh_keys?(cloud)
27
+ begin
28
+ cloud.show.ssh_keys
29
+ true
30
+ rescue RightApi::ApiError => e
31
+ false # assume cloud does not require them
32
+ end
33
+ end
34
+
35
+ # Find SSH key
36
+ #
37
+ # EC2 and Eucalyptus require an SSH key to launch a server. RightScale
38
+ # manages SSH keys for each user so just grabbing the first one is fine,
39
+ # however older configurations might relay on specific keys. You will
40
+ # need to grab the resource UUID from the RightScale dashboard for the key
41
+ # that you want to use.
42
+ def find_ssh_key_by_uuid_or_first(cloud, ssh_uuid = nil)
43
+ ssh_key = nil
44
+ if ssh_uuid
45
+ # grab specific ssh key
46
+ sshkey = find_resource(:ssh_keys, :by_resource_uid, uuid)
47
+ else
48
+ # grab first key found
49
+ keys = cloud.show.ssh_keys
50
+ ssh_key = keys.index.first if keys
51
+ end
52
+ ssh_key
53
+ end
54
+
55
+ # If the cloud reports security groups then we assume it requires them to launch
56
+ # servers.
57
+ def requires_security_groups?(cloud)
58
+ begin
59
+ cloud.show.security_groups
60
+ true
61
+ rescue RightApi::ApiError => e
62
+ false # assume cloud does not require them
63
+ end
64
+ end
65
+
66
+ def user_data(server)
67
+ @user_data ||= server.show.current_instance(:view=>"extended").show.user_data
68
+ end
69
+
70
+ def data_request_url(userdata)
71
+ data_hash = {}
72
+ entry = userdata.split('&').select { |entry| entry =~ /RS_rn_auth/i }
73
+ raise "ERROR: user data token not found. " +
74
+ "Does your MCI have a provides:rs_agent_type=right_link tag?" unless entry
75
+ token = entry.first.split('=')[1]
76
+ "#{api_url}/servers/data_injection_payload/#{token}"
77
+ end
78
+
79
+ def delete_server(name)
80
+ server = find_server_by_name(name)
81
+ server.terminate
82
+ begin
83
+ server_wait_for_state(server, "terminated")
84
+ rescue Exception => e
85
+
86
+ end
87
+ server.destroy
88
+ end
89
+
90
+ def list_servers(filter_by, filter_value)
91
+ list_resources(:servers, filter_by, filter_value)
92
+ end
93
+
94
+ def list_deployments(filter_by=nil, filter_value=nil)
95
+ list_resources(:deployments, filter_by, filter_value)
96
+ end
97
+
98
+ def list_clouds(filter_by=nil, filter_value=nil)
99
+ list_resources(:clouds, filter_by, filter_value)
100
+ end
101
+
102
+ def list_servertemplates(filter_by, filter_value)
103
+ list_resources(:server_templates, filter_by, filter_value)
104
+ end
105
+
106
+ def list_security_groups(cloud, filter_by, filter_value)
107
+ list_subresources(cloud, :security_groups, filter_by, filter_value)
108
+ end
109
+
110
+ def list_multi_cloud_images(server_template, filter_by, filter_value)
111
+ list_subresources(server_template, :multi_cloud_images, filter_by, filter_value)
112
+ end
113
+
114
+ def find_security_group_by_name(cloud, security_group_name)
115
+ find_cloud_resource(cloud, :security_groups, :by_name, security_group_name)
116
+ end
117
+
118
+ def find_server_by_name(name)
119
+ find_resource(:servers, :by_name, name)
120
+ end
121
+
122
+ def find_deployment_by_name(name)
123
+ find_resource(:deployments, :by_name, name)
124
+ end
125
+
126
+ # returns:: String if cloud is found, nil if not found
127
+ def find_cloud_by_name(name)
128
+ find_resource(:clouds, :by_name, name)
129
+ end
130
+
131
+ def find_mci_by_name(name)
132
+ find_resource(:multi_cloud_images, :by_name, name)
133
+ end
134
+
135
+ def find_servertemplate(name_or_id)
136
+ server_template = nil; id = nil; name = nil
137
+
138
+ # detect if user passed in a name or an id
139
+ # there is probably a cleaner way to do this, but I am lazy ATM.
140
+ begin
141
+ id = Integer(name_or_id)
142
+ rescue Exception => e
143
+ name = name_or_id # Cannot be case to integer, assume a name was passed
144
+ end
145
+
146
+ if name
147
+ # find ServerTemplate by name
148
+ st_list = list_resources(:server_templates, :by_name, name)
149
+ revisions = st_list.map { |st| st.revision }
150
+
151
+ # check for duplicate revisions
152
+ duplicates = (revisions.size != revisions.uniq.size)
153
+ raise "ERROR: Duplicate ServerTemplate with the name of '#{name}' detected " +
154
+ "in account -- there can be only one. Please fix via the RightScale dashboard and retry." if duplicates
155
+
156
+ # always use latest revision
157
+ latest_rev = revisions.sort.last
158
+ server_template = st_list.select { |st| st.revision == latest_rev}.first
159
+ raise "ERROR: Unable to find ServerTemplate with the name of '#{name}' found " unless server_template
160
+ else
161
+ # find ServerTemplate by id
162
+ server_template = @client.server_templates.index(:id => id)
163
+ end
164
+
165
+ server_template
166
+ end
167
+
168
+ def create_deployment(name)
169
+ @client.deployments.create(:deployment => { :name => name, :decription => "Created by the Vagrant"})
170
+ end
171
+
172
+ def destroy_deployment(deployment)
173
+ deployment.destroy
174
+ end
175
+
176
+ def create_server(deployment, server_template, mci, cloud, name, ssh_key = nil, groups = nil)
177
+
178
+ # check params
179
+ unless st_href = server_template.show.href
180
+ raise "ERROR: ServerTemplate parameter not initialized properly"
181
+ end
182
+
183
+ unless mci.nil?
184
+ unless mci_href = mci.show.href
185
+ raise "ERROR: Multi Cloud Image parameter not initialized properly"
186
+ end
187
+ end
188
+
189
+ unless d_href = deployment.show.href
190
+ raise "ERROR: Deployment parameter not initialized properly"
191
+ end
192
+
193
+ unless c_href = cloud.show.href
194
+ raise "ERROR: Deployment parameter not initialized properly"
195
+ end
196
+
197
+ if ssh_key
198
+ unless ssh_key_href = ssh_key.show.href
199
+ raise "ERROR: ssh_key parameter not initialized properly"
200
+ end
201
+ end
202
+
203
+ security_group_hrefs = nil
204
+ if groups
205
+ security_group_hrefs = []
206
+ groups.each do |group|
207
+ unless group_href = group.show.href
208
+ raise "ERROR: ssh_key parameter not initialized properly"
209
+ end
210
+ security_group_hrefs << group_href
211
+ end
212
+ end
213
+
214
+ instance_hash = {
215
+ :cloud_href => c_href,
216
+ :server_template_href => st_href
217
+ }
218
+ instance_hash[:ssh_key_href] = ssh_key_href if ssh_key
219
+ instance_hash[:security_group_hrefs] = security_group_hrefs if security_group_hrefs
220
+
221
+ # Use the MCI if provided otherwise let the API choose the default MCI
222
+ # in the ServerTemplate.
223
+ instance_hash[:multi_cloud_image_href] = mci_href unless mci_href.nil?
224
+
225
+ # create server in deployment using specfied ST
226
+ server =
227
+ @client.servers.create({
228
+ :server => {
229
+ :name => name,
230
+ :decription => "Created by the right_provision_api", #TODO: pass this as a param
231
+ :deployment_href => d_href,
232
+ :instance => instance_hash
233
+ }
234
+ })
235
+ end
236
+
237
+ def is_provisioned?(server)
238
+ server.show.api_methods.include?(:current_instance)
239
+ end
240
+
241
+ # @param(Hash) inputs Hash input name/value pairs i.e. { :name => "text:dummy"}
242
+ def launch_server(server, inputs = { :name => "text:dummy"})
243
+ server_name = server.show.name
244
+ server.launch(inputs) # TODO: parse inputs from Vagrantfile
245
+ # XXX: need to create a new server object after launch -- why? API bug?
246
+ find_server_by_name(server_name)
247
+ end
248
+
249
+ def terminate_server(server)
250
+ server.terminate
251
+ end
252
+
253
+ # Only use this *before* you launch the server
254
+ def set_server_inputs(server, inputs)
255
+ server.show.next_instance.show.inputs.multi_update({"inputs" => inputs})
256
+ end
257
+
258
+ def server_wait_for_state(server, target_state, delay = 10)
259
+ current_state = server_state(server)
260
+ while current_state != target_state
261
+ raise "Unexpected sever state: #{current_state}" if is_bad?(current_state)
262
+ puts "Server #{current_state}. Waiting for instance to be in #{target_state} state..."
263
+ sleep delay
264
+ current_state = server_state(server)
265
+ end
266
+ end
267
+
268
+ def set_bad_states(list_array)
269
+ @bad_states = list_array
270
+ end
271
+
272
+ def is_bad?(state)
273
+ @bad_states ||= []
274
+ @bad_states.select{|s| state =~ /#{s}/}.size > 0
275
+ end
276
+
277
+ def server_ready?(server)
278
+ server_state(server) == "operational"
279
+ end
280
+
281
+ def server_cloud_name(server)
282
+ instance = instance_from_server(server)
283
+ cloud = cloud_from_instance(instance)
284
+ cloud.show.name
285
+ end
286
+
287
+ def server_info(server)
288
+ server.show.current_instance.show(:view => 'extended')
289
+ end
290
+
291
+ private
292
+
293
+ def server_state(server)
294
+ instance_from_server(server).show.state
295
+ end
296
+
297
+ def instance_from_server(server)
298
+ server_data = server.show
299
+ if is_provisioned?(server)
300
+ begin
301
+ server_data.current_instance
302
+ rescue
303
+ server_data.next_instance
304
+ end
305
+ else
306
+ server_data.next_instance
307
+ end
308
+ end
309
+
310
+ def cloud_from_instance(instance)
311
+ instance.show.cloud
312
+ end
313
+
314
+ def find_resource(api_resource, filter_key, filter_value)
315
+ resource = nil
316
+ list = list_resources(api_resource, filter_key, filter_value)
317
+ raise "More than one #{api_resource} with the #{filter_key} of '#{filter_value}'. " +
318
+ "Please resolve via the RightScale dashboard and retry." if list.size > 1
319
+ resource = list.first unless list.empty?
320
+ resource
321
+ end
322
+
323
+ def list_resources(api_resource, filter_key=nil, filter_value=nil)
324
+ raise ArgumentError.new("api_resource must be a symbol") unless api_resource.kind_of?(Symbol)
325
+ key = filter_key.to_s.delete("by_") # convert :by_name to "name"
326
+ filter = {}
327
+ filter = {:filter => ["#{key}==#{filter_value}"]} if filter_value
328
+ list = @client.send(api_resource).index(filter)
329
+ list
330
+ end
331
+
332
+ def index_resource(api_resource, index_key, index_value)
333
+ raise ArgumentError.new("api_resource must be a symbol") unless api_resource.kind_of?(Symbol)
334
+ arry = @client.send(api_resource).index(index_key => index_value)
335
+ arry
336
+ end
337
+
338
+ def find_cloud_resource(cloud, api_resource, filter_key, filter_value)
339
+ resource = nil
340
+ list = list_subresources(cloud, api_resource, filter_key, filter_value)
341
+ raise "More than one #{api_resource} with the #{filter_key} of '#{filter_value}'. " +
342
+ "Please resolve via the RightScale dashboard and retry." if list.size > 1
343
+ resource = list.first unless list.empty?
344
+ resource
345
+ end
346
+
347
+ def list_subresources(api_resource, subresource, filter_key, filter_value)
348
+ raise ArgumentError.new("subresource must be a symbol") unless subresource.kind_of?(Symbol)
349
+ key = filter_key.to_s.delete("by_") # convert :by_name to "name"
350
+ filter = {}
351
+ filter = {:filter => ["#{key}==#{filter_value}"]} if filter_value
352
+ list = api_resource.show.send(subresource).index(filter)
353
+ list
354
+ end
355
+
356
+ end
357
+ end
@@ -0,0 +1,54 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ require 'logger'
18
+
19
+ module RightApiHelper
20
+
21
+ # Base helper class
22
+ #
23
+ class Base
24
+
25
+ def initialize(right_api_client)
26
+ @client = right_api_client
27
+ logger
28
+ end
29
+
30
+ def api_url
31
+ @client.api_url
32
+ end
33
+
34
+ def logger(logger=nil)
35
+ @log = logger
36
+ @log ||= default_logger
37
+ end
38
+
39
+ def log_level(level)
40
+ @log.level = level
41
+ end
42
+
43
+ private
44
+
45
+ def default_logger
46
+ logger = Logger.new(STDOUT)
47
+ logger.formatter = proc do |severity, datetime, progname, msg|
48
+ "#{msg}\n"
49
+ end
50
+ logger
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module RightApiHelper
19
+ class Cache < Base
20
+
21
+ def initialize(cache_uuid, cache_tmp_dir=".")
22
+ logger
23
+ @cache_file = File.join(cache_tmp_dir, "#{cache_uuid}.yml")
24
+ end
25
+
26
+ # Get all instances for all clouds registered in account
27
+ def get
28
+ hash = nil
29
+ if File.exists?(@cache_file)
30
+ @log.info "Reading cache from #{@cache_file}"
31
+ hash = YAML::load(File.open(@cache_file))
32
+ else
33
+ @log.info "No cache found at #{@cache_file}"
34
+ end
35
+ hash
36
+ end
37
+
38
+ def set(hash)
39
+ @log.info "Writing cache to #{@cache_file}"
40
+ File.open(@cache_file, "w") { |f| f.write(YAML.dump(hash)) }
41
+ end
42
+
43
+ def clear
44
+ if File.exists?(@cache_file)
45
+ @log.info("removing cache file at #{@cache_file}")
46
+ File.delete(@cache_file)
47
+ else
48
+ @log.info("no cache file to remove at #{@cache_file}")
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module RightApiHelper
19
+ class Deployments < Base
20
+
21
+ def initialize(right_api_client)
22
+ super(right_api_client)
23
+ @api_shim = RightApiHelper::API15.new(right_api_client)
24
+ end
25
+
26
+ # Return: MediaType : right_api_client deployment
27
+ def find_or_create(name)
28
+ @log.info "Looking for deployment: '#{name}'..."
29
+ deployment = @api_shim.find_deployment_by_name(name)
30
+ @log.info "Deployment #{deployment.nil? ? "not found" : "found"}."
31
+ unless deployment
32
+ deployment = @api_shim.create_deployment(name)
33
+ @log.info "Deployment created: '#{name}'"
34
+ end
35
+ deployment
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module RightApiHelper
19
+ class Instances < Base
20
+
21
+ def initialize(right_api_client)
22
+ super(right_api_client)
23
+ @api_shim = RightApiHelper::API15.new(right_api_client)
24
+ end
25
+
26
+ # Get all instances for all clouds registered in account
27
+ def get_instances
28
+ instances = [ ]
29
+ get_clouds.each do |cloud|
30
+ instances += cloud.instances.index(:filter => [], :view => 'tiny')
31
+ end
32
+ instances
33
+ end
34
+
35
+ def get_unmanaged_instances
36
+ get_instances.reject { |i| i.respond_to?(:deployment) }
37
+ end
38
+
39
+ private
40
+
41
+ def get_clouds
42
+ @client.clouds.index
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module RightApiHelper
19
+ class DeploymentsCreator
20
+
21
+ def run(argv)
22
+ if argv.empty? or argv[0].empty?
23
+ log_error "FATAL: you must supply path to json file"
24
+ exit -1
25
+ end
26
+ filename = ""
27
+ filename = argv[0] if argv[0]
28
+ unless File.exists?(filename)
29
+ log_error "FATAL: no such file: '#{filename}'"
30
+ exit -2
31
+ end
32
+
33
+ @json = File.open(filename, "r") { |f| f.read }
34
+
35
+ end
36
+
37
+ private
38
+
39
+ def log_error(message)
40
+ puts message
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Author: cary@rightscale.com
3
+ # Copyright 2014 RightScale, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module RightApiHelper
19
+ class Session < Base
20
+
21
+ def initialize
22
+ logger
23
+ end
24
+
25
+ # Create a right_api_client session
26
+ # See https://github.com/rightscale/right_api_client/blob/master/README.md#usage-instructions
27
+ #
28
+ # Returns: handle to right_api_client gem
29
+ #
30
+ def create_client(email, password, account_id, api_url=nil)
31
+ begin
32
+ args = { :email => email, :password => password, :account_id => account_id }
33
+ args[:api_url] = api_url if api_url
34
+ @client ||= RightApi::Client.new(args)
35
+ setup_client_logging
36
+ @client
37
+ rescue Exception => e
38
+ args.delete(:password) # don't log password
39
+ puts "ERROR: could not connect to RightScale API. Params: #{args.inspect}"
40
+ puts e.message
41
+ puts e.backtrace
42
+ raise e
43
+ end
44
+ end
45
+
46
+ # Create a right_api_client session from YAML file
47
+ # See https://github.com/rightscale/right_api_client/blob/master/README.md#usage-instructions
48
+ #
49
+ # Returns: handle to right_api_client gem
50
+ #
51
+ def create_client_from_file(filename)
52
+ begin
53
+ @client ||= RightApi::Client.new(YAML.load_file(File.expand_path(filename, __FILE__)))
54
+ setup_client_logging
55
+ @client
56
+ rescue Exception => e
57
+ puts "ERROR: could not connect to RightScale API. filename: '#{filename}'"
58
+ puts e.message
59
+ puts e.backtrace
60
+ raise e
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def setup_client_logging
67
+ # right_api_client logs too much stuff, throw away unless in debug mode
68
+ if @log.level == Logger::DEBUG
69
+ @client.log(STDOUT)
70
+ else
71
+ @client.log("/dev/null")
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module RightApiHelper
2
+ VERSION = "0.0.1"
3
+ end