right_api_helper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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