rest_connection 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rest_connection'
3
+ require 'trollop'
4
+
5
+ opts = Trollop::options do
6
+ opt :deployment, "deployment nickname", :type => :string, :required => true
7
+ opt :template, "server template href to set for all servers", :type => :string, :required => true
8
+ end
9
+
10
+ deployment = Deployment.find_by_nickname_speed(opts[:deployment]).first
11
+
12
+ deployment.servers.each do |s|
13
+ s.set_template(opts[:template])
14
+ end
@@ -0,0 +1,208 @@
1
+ # This file is part of RestConnection
2
+ #
3
+ # RestConnection is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # RestConnection is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with RestConnection. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'net/https'
17
+ require 'rubygems'
18
+ require 'json'
19
+ require 'yaml'
20
+ require 'cgi'
21
+ require 'rest_connection/rightscale/rightscale_api_base'
22
+ require 'rest_connection/rightscale/rightscale_api_resources'
23
+ require 'logger'
24
+
25
+ module RestConnection
26
+ class Connection
27
+ # Settings is a hash of options for customizing the connection.
28
+ # settings.merge! {
29
+ # :common_headers => { "X_CUSTOM_HEADER" => "BLAH" },
30
+ # :api_url =>
31
+ # :user =>
32
+ # :pass =>
33
+ attr_accessor :settings
34
+
35
+ # RestConnection api settings configuration file:
36
+ # Settings are loaded from a yaml configuration file in users home directory.
37
+ # Copy the example config from the gemhome/config/rest_api_config.yaml.sample to ~/.rest_connection/rest_api_config.yaml
38
+ # OR to /etc/rest_connection/rest_api_config.yaml
39
+ #
40
+ def initialize(config_yaml = File.join(File.expand_path("~"), ".rest_connection", "rest_api_config.yaml"))
41
+ @@logger = nil
42
+ etc_config = File.join("#{File::SEPARATOR}etc", "rest_connection", "rest_api_config.yaml")
43
+ if File.exists?(config_yaml)
44
+ @settings = YAML::load(IO.read(config_yaml))
45
+ elsif File.exists?(etc_config)
46
+ @settings = YAML::load(IO.read(etc_config))
47
+ else
48
+ logger("\nWARNING: you must setup config file rest_api_config.yaml in #{config_yaml} or #{etc_config}")
49
+ logger("WARNING: see GEM_HOME/rest_connection/config/rest_api_config.yaml for example config")
50
+ @settings = {}
51
+ end
52
+ end
53
+
54
+ # Main HTTP connection loop. Common settings are set here, then we yield(BASE_URI, OPTIONAL_HEADERS) to other methods for each type of HTTP request: GET, PUT, POST, DELETE
55
+ #
56
+ # The block must return a Net::HTTP Request. You have a chance to taylor the request inside the block that you pass by modifying the url and headers.
57
+ #
58
+ # rest_connect do |base_uri, headers|
59
+ # headers.merge! {:my_header => "blah"}
60
+ # Net::HTTP::Get.new(base_uri, headers)
61
+ # end
62
+ #
63
+ def rest_connect(&block)
64
+ uri = URI.parse(@settings[:api_url])
65
+ http = Net::HTTP.new(uri.host, uri.port)
66
+ if uri.scheme == 'https'
67
+ http.use_ssl = true
68
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
+ end
70
+ headers = @settings[:common_headers]
71
+ http.start do |http|
72
+ req = yield(uri, headers)
73
+ req.basic_auth(@settings[:user], @settings[:pass]) if @settings[:user]
74
+ logger("#{req.method}: #{req.path}")
75
+ logger("\trequest body: #{req.body}") if req.body
76
+ response, body = http.request(req)
77
+ handle_response(response)
78
+ end
79
+ end
80
+
81
+ # connection.get("/root/login", :test_header => "x", :test_header2 => "y")
82
+ # href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
83
+ # href = "servers" this will be concat'd on to the api_url from the settings
84
+ # additional_parameters = Hash or String of parameters to pass to HTTP::Get
85
+ def get(href, additional_parameters = "")
86
+ rest_connect do |base_uri,headers|
87
+ href = "#{base_uri}/#{href}" unless begins_with_slash(href)
88
+ new_path = URI.escape(href + '.js?' + requestify(additional_parameters))
89
+ Net::HTTP::Get.new(new_path, headers)
90
+ end
91
+ end
92
+
93
+ # connection.post(server_url + "/start")
94
+ #
95
+ # href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
96
+ # href = "servers" this will be concat'd on to the api_url from the settings
97
+ # additional_parameters = Hash or String of parameters to pass to HTTP::Post
98
+ def post(href, additional_parameters = {})
99
+ rest_connect do |base_uri, headers|
100
+ href = "#{base_uri}/#{href}" unless begins_with_slash(href)
101
+ res = Net::HTTP::Post.new(href , headers)
102
+ unless additional_parameters.empty?
103
+ res.set_content_type('application/json')
104
+ res.body = additional_parameters.to_json
105
+ end
106
+ #res.set_form_data(additional_parameters, '&')
107
+ res
108
+ end
109
+ end
110
+
111
+ # connection.put(server_url + "/start")
112
+ #
113
+ # href = "/api/base" if this begins with a slash then the url will be used as absolute path.
114
+ # href = "servers" this will be concat'd on to the api_url from the settings
115
+ # additional_parameters = Hash or String of parameters to pass to HTTP::Put
116
+ def put(href, additional_parameters = {})
117
+ rest_connect do |base_uri, headers|
118
+ href = "#{base_uri}/#{href}" unless begins_with_slash(href)
119
+ new_path = URI.escape(href)
120
+ req = Net::HTTP::Put.new(new_path, headers)
121
+ req.set_content_type('application/json')
122
+ req.body = additional_parameters.to_json
123
+ req
124
+ end
125
+ end
126
+
127
+ # connection.delete(server_url)
128
+ #
129
+ # href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
130
+ # href = "servers" this will be concat'd on to the api_url from the settings
131
+ # additional_parameters = Hash or String of parameters to pass to HTTP::Delete
132
+ def delete(href, additional_parameters = {})
133
+ rest_connect do |base_uri, headers|
134
+ href = "#{base_uri}/#{href}" unless begins_with_slash(href)
135
+ new_path = URI.escape(href)
136
+ req = Net::HTTP::Delete.new(href, headers)
137
+ req.set_content_type('application/json')
138
+ req.body = additional_parameters.to_json
139
+ req
140
+ end
141
+ end
142
+
143
+ # handle_response
144
+ # res = HTTP response
145
+ #
146
+ # decoding and post processing goes here. This is where you may need some customization if you want to handle the response differently (or not at all!). Luckily it's easy to modify based on this handler.
147
+ def handle_response(res)
148
+ if res.code.to_i == 201
149
+ return res['Location']
150
+ elsif [200,203,204].detect { |d| d == res.code.to_i }
151
+ if res.body
152
+ begin
153
+ return JSON.load(res.body)
154
+ rescue => e
155
+ return res
156
+ end
157
+ else
158
+ return res
159
+ end
160
+ else
161
+ raise "invalid response HTTP code: #{res.code.to_i}, #{res.code}, #{res.body}"
162
+ end
163
+ end
164
+
165
+ def begins_with_slash(href)
166
+ href =~ /^\//
167
+ end
168
+
169
+ def logger(message)
170
+ init_message = "Initializing Logging using "
171
+ if @@logger.nil?
172
+ if @settings[:log_file]
173
+ @@logger = Logger.new(@settings[:log_file])
174
+ init_message += @settings[:log_file]
175
+ elsif ENV['REST_CONNECTION_LOG']
176
+ @@logger = Logger.new(ENV['REST_CONNECTION_LOG'])
177
+ init_message += ENV['REST_CONNECTION_LOG']
178
+ else @settings[:log_file]
179
+ @@logger = Logger.new(STDOUT)
180
+ init_message += "STDOUT"
181
+ end
182
+ STDOUT.puts init_message
183
+ end
184
+
185
+ @@logger.info(message)
186
+ end
187
+
188
+ # used by requestify to build parameters strings
189
+ def name_with_prefix(prefix, name)
190
+ prefix ? "#{prefix}[#{name}]" : name.to_s
191
+ end
192
+
193
+ # recursive method builds CGI escaped strings from Hashes, Arrays and strings of parameters.
194
+ def requestify(parameters, prefix=nil)
195
+ if Hash === parameters
196
+ return nil if parameters.empty?
197
+ parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
198
+ elsif Array === parameters
199
+ parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
200
+ elsif prefix.nil?
201
+ parameters
202
+ else
203
+ "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
204
+ end
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,43 @@
1
+ require 'mechanize'
2
+ require 'logger'
3
+ require 'uri'
4
+
5
+ module MechanizeConnection
6
+ module Connection
7
+
8
+ # creates/returns global mechanize agent
9
+ def agent
10
+ @@agent ||= WWW::Mechanize.new do |a|
11
+ a.log = Logger.new(STDOUT)
12
+ a.log.level = Logger::INFO
13
+ end
14
+ end
15
+
16
+ # creates/returns global mechanize agent
17
+ def self.agent
18
+ @@agent ||= WWW::Mechanize.new do |a|
19
+ a.log = Logger.new(STDOUT)
20
+ a.log.level = Logger::INFO
21
+ end
22
+ end
23
+
24
+ # login to rightscale dashboard /sessions/new using rest connection user and pass
25
+ def wind_monkey
26
+ base_url = URI.parse(connection.settings[:api_url])
27
+ base_url.path = "/"
28
+ if agent.cookie_jar.empty?(base_url)
29
+ agent.user_agent_alias = 'Mac Safari'
30
+ # Login
31
+ base_url = URI.parse(connection.settings[:api_url])
32
+ agent.user_agent_alias = 'Mac Safari'
33
+ # Login
34
+ base_url.path = "/sessions/new"
35
+ login_page = agent.get(base_url)
36
+ login_form = login_page.forms.first
37
+ login_form.email = connection.settings[:user]
38
+ login_form.password = connection.settings[:pass]
39
+ agent.submit(login_form, login_form.buttons.first)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # This file is part of RestConnection
2
+ #
3
+ # RestConnection is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # RestConnection is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with RestConnection. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ class Deployment < RightScale::Api::Base
17
+ def set_input(name, value)
18
+ deploy_href = URI.parse(self.href)
19
+ connection.put(deploy_href.path, :deployment => {:parameters => {name => value} })
20
+ end
21
+
22
+ def servers_no_reload
23
+ server_list = []
24
+ @params['servers'].each do |s|
25
+ server_list << Server.new(s)
26
+ end
27
+ return server_list
28
+ end
29
+
30
+ def servers
31
+ # this populates extra information about the servers
32
+ servers_no_reload.each do |s|
33
+ s.reload
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ class Executable < RightScale::Api::Base
2
+
3
+ # executable can be EITHER a right_script or recipe
4
+ # executable example params format:
5
+ # can have recipes AND right_scripts
6
+ # @params =
7
+ # { :recipe =>
8
+ # :position => 12,
9
+ # :apply => "operational",
10
+ # :right_script => { "href" => "http://blah",
11
+ # "name" => "blah"
12
+ # ...
13
+ # }
14
+
15
+ def recipe?
16
+ if self["recipe"].nil? && right_script['href']
17
+ return false
18
+ end
19
+ true
20
+ end
21
+
22
+ def right_script?
23
+ if self["recipe"].nil? && right_script['href']
24
+ return true
25
+ end
26
+ false
27
+ end
28
+
29
+ def name
30
+ if right_script?
31
+ return right_script.name
32
+ else
33
+ return recipe
34
+ end
35
+ end
36
+
37
+ def href
38
+ if right_script?
39
+ return right_script.href
40
+ else
41
+ #recipes do not have hrefs, only names
42
+ return recipe
43
+ end
44
+ end
45
+
46
+ def right_script
47
+ RightScript.new(@params['right_script'])
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ # This file is part of RestConnection
2
+ #
3
+ # RestConnection is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # RestConnection is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with RestConnection. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # This is an instance facing api and can only be used with
17
+ # an authentication URL normally found in the instance's userdata called
18
+ # RS_API_URL
19
+ class Instance < RightScale::Api::Base
20
+ #def create_ebs_volume_from_snap(snap_aws_id)
21
+ # connection.post('create_ebs_volume.js', :aws_id => snap_aws_id )
22
+ #end
23
+
24
+ def attach_ebs_volume(params)
25
+ connection.put('attach_ebs_volume.js', params)
26
+ end
27
+
28
+ def create_ebs_snapshot(params)
29
+ connection.post('create_ebs_snapshot.js', params)
30
+ end
31
+
32
+ def detach_ebs_volume(params)
33
+ connection.put('detach_ebs_volume.js', params)
34
+ end
35
+
36
+ def delete_ebs_volume(params)
37
+ connection.delete('delete_ebs_volume.js', params)
38
+ end
39
+
40
+ def create_ebs_volume(params)
41
+ connection.post('create_ebs_volume.js', params)
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # This file is part of RestConnection
2
+ #
3
+ # RestConnection is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # RestConnection is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with RestConnection. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+
17
+ class RightScript < RightScale::Api::Base
18
+ def self.from_yaml(yaml)
19
+ scripts = []
20
+ x = YAML.load(yaml)
21
+ x.keys.each do |script|
22
+ scripts << self.new('href' => "right_scripts/#{script}", 'name' => x[script].ivars['name'])
23
+ end
24
+ scripts
25
+ end
26
+
27
+ def self.from_instance_info(file = "/var/spool/ec2/rs_cache/info.yml")
28
+ scripts = []
29
+ if File.exists?(file)
30
+ x = YAML.load(IO.read(file))
31
+ elsif File.exists?(File.join(File.dirname(__FILE__),'info.yml'))
32
+ x = YAML.load(IO.read(File.join(File.dirname(__FILE__),'info.yml')))
33
+ else
34
+ return nil
35
+ end
36
+ x.keys.each do |script|
37
+ scripts << self.new('href' => "right_scripts/#{script}", 'name' => x[script].ivars['name'])
38
+ end
39
+ scripts
40
+ end
41
+
42
+ end
@@ -0,0 +1,120 @@
1
+ # This file is part of RestConnection
2
+ #
3
+ # RestConnection is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # RestConnection is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with RestConnection. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'rest_connection/mechanize_connection'
17
+ require 'activesupport'
18
+
19
+ module RightScale
20
+ module Api
21
+ class Base
22
+ include MechanizeConnection::Connection
23
+ # The params hash of attributes for direct manipulation
24
+ attr_accessor :params
25
+
26
+ def self.connection()
27
+ @@connection ||= RestConnection::Connection.new
28
+ end
29
+ def connection()
30
+ @@connection ||= RestConnection::Connection.new
31
+ end
32
+
33
+ def initialize(params = {})
34
+ @params = params
35
+ end
36
+
37
+ def self.resource_plural_name
38
+ self.to_s.underscore.pluralize
39
+ end
40
+
41
+ def self.resource_singluar_name
42
+ self.to_s.underscore
43
+ end
44
+
45
+ def self.find_all
46
+ a = Array.new
47
+ connection.get(self.resource_plural_name).each do |object|
48
+ a << self.new(object)
49
+ end
50
+ return a
51
+ end
52
+
53
+ def self.find_by_nickname(nickname)
54
+ self.find_all.select do |s|
55
+ s.nickname == nickname
56
+ end
57
+ end
58
+
59
+ def reload
60
+ uri = URI.parse(self.href)
61
+ @params ? @params.merge!(connection.get(uri.path)) : @params = connection.get(uri.path)
62
+ end
63
+
64
+ def self.find(href)
65
+ uri = URI.parse(href)
66
+ self.new(connection.get(uri.path))
67
+ end
68
+
69
+ def self.find_by_id(id)
70
+ self.new(connection.get(self.resource_plural_name + "/#{id}"))
71
+ end
72
+
73
+ def self.create(opts)
74
+ location = connection.post(self.resource_plural_name, self.resource_singluar_name.to_sym => opts)
75
+ self.new('href' => location)
76
+ end
77
+
78
+ # filter is only implemented on some api endpoints
79
+ def self.find_by_nickname_speed(nickname)
80
+ self.find_with_filter('nickname' => nickname)
81
+ end
82
+
83
+ # filter is only implemented on some api endpoints
84
+ def self.find_with_filter(filter = {})
85
+ filter_params = ""
86
+ filter.each {|key,val| filter_params += "filter[]=#{key}=#{val}&"}
87
+ a = Array.new
88
+ connection.get(self.resource_plural_name, filter_params).each do |object|
89
+ a << self.new(object)
90
+ end
91
+ return a
92
+ end
93
+
94
+ def destroy
95
+ my_href = URI.parse(self.href)
96
+ connection.delete(my_href.path)
97
+ end
98
+
99
+ # the following two methods are used to access the @params hash in a friendly way
100
+ def method_missing(method_name, *args)
101
+ if @params[method_name.to_s]
102
+ return @params[method_name.to_s]
103
+ elsif @params[method_name.to_s.gsub(/_/,'-')]
104
+ return @params[method_name.to_s.gsub(/_/,'-')]
105
+ else
106
+ raise "called unknown method #{method_name} with #{args.inspect}"
107
+ end
108
+ end
109
+
110
+ def [](name)
111
+ if @params[name]
112
+ return @params[name]
113
+ else
114
+ return nil
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end