resat 0.7.0

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,39 @@
1
+ = Synopsis
2
+
3
+ This example uses the RightScale REST API:
4
+ http://wiki.rightscale.com/2._References/01-RightScale/03-RightScale_API
5
+
6
+ This example will list all your servers, create a new server using the
7
+ 'Rails all-in-one' server template and delete it.
8
+
9
+ See the main README.rdoc for instructions on how to setup resat prior to
10
+ running the examples.
11
+
12
+ = How to
13
+
14
+ * Run:
15
+
16
+ $ resat scenarios -d user:<your RightScale username> -d pass:<your RightScale password> -d acct:<your RightScale account number>
17
+
18
+ * See:
19
+
20
+ $ cat output.yml
21
+
22
+ * See more:
23
+
24
+ $ vi /tmp/resat.log
25
+
26
+ or
27
+
28
+ $ vi resat.log
29
+
30
+ if <tt>/tmp</tt> does not exist
31
+
32
+ = Additional Examples
33
+
34
+ The <i>run_server</i> example in the <i>additional</i> folder will create and
35
+ launch a server in the <i>default</i> deployment and wait until it's
36
+ operational before running an operational script on it. It will then stop and
37
+ delete it. See the file <tt>additional/run_server.yml</tt>
38
+ (http://github.com/raphael/resat/blob/master/examples/rightscale/additional/run_server.yml)
39
+ for additional information.
@@ -0,0 +1,75 @@
1
+ # This scenario will launch an All-in-on Rails server, wait for it to become
2
+ # operational, run a RightScript (Mongrels restart) and then stop the server,
3
+ # wait for it to be stopped and delete it.
4
+ #
5
+ # Note: This scenario reuses the 'create_server' scenario to create the
6
+ # Rails All-in-one server.
7
+ #
8
+ name: Launch rails servers and run RightScript
9
+ includes:
10
+ - ../scenarios/create_server
11
+ steps:
12
+ - request: # Step 1. Start server
13
+ resource: servers
14
+ id: $server_id
15
+ custom:
16
+ name: start
17
+ type: post
18
+
19
+ - request: # Step 2. Retrieve Mongrels restart RightScript
20
+ operation: index
21
+ resource: right_scripts
22
+ filters:
23
+ - name: Get Rails server template
24
+ target: body
25
+ extractors:
26
+ - field: right-scripts/right-script[name='RB mongrel_cluster (re)start v1']/href
27
+ variable: right_script_id
28
+
29
+ - request: # Step 3. Wait for server to become operational
30
+ resource: servers
31
+ id: $server_id
32
+ operation: show
33
+ guards:
34
+ - name: Wait for operational state
35
+ target: body
36
+ field: server/state
37
+ pattern: "operational"
38
+ period: 10
39
+ timeout: 900
40
+
41
+ - request: # Step 4. Restart Mongrels
42
+ resource: servers
43
+ id: $server_id
44
+ custom:
45
+ name: run_script
46
+ type: post
47
+ params:
48
+ - name: right_script
49
+ value: $right_script_id
50
+
51
+ - request: # Step 5. Stop server
52
+ resource: servers
53
+ id: $server_id
54
+ custom:
55
+ name: stop
56
+ type: post
57
+
58
+ - request: # Step 6. Wait for server to become stopped
59
+ resource: servers
60
+ id: $server_id
61
+ operation: show
62
+ guards:
63
+ - name: Wait for stop state
64
+ target: body
65
+ field: server/state
66
+ pattern: "stopped"
67
+ period: 10
68
+ timeout: 900
69
+
70
+ - request: # Step 7. Now delete the server
71
+ operation: destroy
72
+ resource: servers
73
+ id: $server_id
74
+
75
+
@@ -0,0 +1,34 @@
1
+ # Hostname used for API calls
2
+ host: moo.rightscale.com
3
+
4
+ # Port
5
+ # port: 443
6
+
7
+ # Common base URL to all API calls
8
+ base_url: api/acct/$acct
9
+
10
+ # Use HTTPS?
11
+ use_ssl: yes
12
+
13
+ # Basic auth username if any
14
+ username: $user
15
+
16
+ # Basic auth password if any
17
+ password: $pass
18
+
19
+ # Common request headers for all API calls
20
+ headers:
21
+ - name: X-API-VERSION
22
+ value: '1.0'
23
+
24
+ # Common parameters for all API calls
25
+ params:
26
+
27
+ # Global variables
28
+ variables:
29
+
30
+ # Input file
31
+ #input: variables.yml
32
+
33
+ # Output file
34
+ #output: variables.yml
@@ -0,0 +1,48 @@
1
+ # Create new Rails all-in-one server in default deployment
2
+ name: Create Rails Server
3
+ steps:
4
+ - request: # Step 1. Retrieve default deployment
5
+ operation: index
6
+ resource: deployments
7
+ valid_codes:
8
+ - 200
9
+ filters:
10
+ - name: Get default deployment
11
+ target: body
12
+ extractors:
13
+ - field: deployments/deployment/href
14
+ variable: deployment
15
+
16
+ - request: # Step 2. Retrieve Rails template
17
+ operation: index
18
+ resource: server_templates
19
+ filters:
20
+ - name: Get Rails server template
21
+ target: body
22
+ extractors:
23
+ - field: server-templates/ec2-server-template[nickname='Rails all-in-one-developer v8']/href
24
+ variable: server_template
25
+
26
+ - request: # Step 3. Create server
27
+ operation: create
28
+ resource: servers
29
+ valid_codes:
30
+ - 201
31
+ params:
32
+ - name: server[nickname]
33
+ value: 'Resat Rails Server'
34
+ - name: server[server_template_href]
35
+ value: $server_template
36
+ - name: server[deployment_href]
37
+ value: $deployment
38
+ filters:
39
+ - name: validate server response
40
+ target: body
41
+ is_empty: true
42
+ - name: Get server id
43
+ target: header
44
+ extractors:
45
+ - field: location
46
+ pattern: '.*\/(\d+)$'
47
+ variable: server_id
48
+ export: true # Export id to other scenarios
@@ -0,0 +1,13 @@
1
+ # Delete previously created server
2
+ name: Delete Rails Server
3
+ steps:
4
+ - request:
5
+ operation: destroy
6
+ resource: servers
7
+ id: $server_id
8
+ valid_codes:
9
+ - 200
10
+ filters:
11
+ - name: validate destroy response
12
+ target: body
13
+ is_empty: true
@@ -0,0 +1,9 @@
1
+ # List servers in default deployment
2
+ name: List servers
3
+ steps:
4
+ - request:
5
+ operation: index
6
+ resource: servers
7
+ valid_codes:
8
+ - 200
9
+
@@ -0,0 +1,50 @@
1
+ = Synopsis
2
+
3
+ This example uses the Twitter REST API:
4
+
5
+ http://apiwiki.twitter.com/REST+API+Documentation
6
+
7
+ <b>Please note</b>: This example will send a tweet on your behalf with the text:
8
+
9
+ Checking out resat (http://tinyurl.com/dg8gf9)
10
+
11
+ by default. Override the default text in the <tt>config/resat.yaml</tt> file or
12
+ via the command line:
13
+
14
+ $ resat scenarios -d tweet:'My custom tweet' -d user:... -d pass:...
15
+
16
+ See the main README.rdoc for instructions on how to setup resat prior to
17
+ running the examples.
18
+
19
+ = How to
20
+
21
+ * Run:
22
+
23
+ $ resat scenarios -d user:<your twitter username> -d pass:<your twitter password>
24
+
25
+ * See:
26
+
27
+ $ cat output.yml
28
+
29
+ * See more:
30
+
31
+ $ vi /tmp/resat.log
32
+
33
+ or
34
+
35
+ $ vi resat.log
36
+
37
+ if <tt>/tmp</tt> does not exist
38
+
39
+ = Additional Examples
40
+
41
+ The <i>additional</i> folder contains two additional scenarios which are not ran
42
+ by default:
43
+
44
+ * <tt>follow.yml</tt>: Follow given user
45
+ * <tt>send_message.yml</tt>: Send direct message to given user with given content
46
+
47
+ Both these scenarios require inputs. Inputs are given using the <tt>--define</tt>
48
+ (or <tt>-d</tt>) resat option:
49
+
50
+ $ resat additional/follow -d followed:rgsimon
@@ -0,0 +1,15 @@
1
+ # Follow username specified on command line
2
+ #
3
+ # Usage:
4
+ # resat follow -d followed:rgsimon
5
+
6
+ name: Follow given user
7
+ config: ../config/resat.yaml
8
+ steps:
9
+ - request:
10
+ resource: notifications # Act on the 'notifications' resource
11
+ custom: # Use a custom operation (i.e. not a CRUD operation)
12
+ name: follow # Operation name
13
+ type: post # POST request
14
+ id: $followed # ID of user to follow
15
+ format: xml # Get response in XML format
@@ -0,0 +1,19 @@
1
+ # Send direct message to username specified on command line
2
+ # Note: user must be following you otherwise the request returns 403.
3
+ #
4
+ # Usage:
5
+ # resat send_message -d to:rgsimon -d text:'Hello from resat!'
6
+
7
+ name: Send Direct Message
8
+ config: ../config/resat.yaml
9
+ steps:
10
+ - request:
11
+ resource: direct_messages # Act on the 'direct_messages' resource
12
+ custom: # Use a custom operation (i.e. not a CRUD operation)
13
+ name: new.xml # Operation name
14
+ type: post # POST request
15
+ params:
16
+ - name: user # 'user' parameter
17
+ value: $to # Username
18
+ - name: text # 'text' parameter
19
+ value: $text # Message content
@@ -0,0 +1,40 @@
1
+ # Hostname used for API calls
2
+ host: twitter.com
3
+
4
+ # Port
5
+ # port: 443
6
+
7
+ # Common base URL to all API calls
8
+ base_url:
9
+
10
+ # Use HTTPS?
11
+ use_ssl: yes
12
+
13
+ # Basic auth username if any
14
+ # Uses variable 'user' from the command line
15
+ username: $user
16
+
17
+ # Basic auth password if any
18
+ # Uses variable 'pass' from the command line
19
+ password: $pass
20
+
21
+ # Common request headers for all API calls
22
+ headers:
23
+ # - name: header_name
24
+ # value: header_value
25
+
26
+ # Common parameters for all API calls
27
+ params:
28
+ # - name: param_name
29
+ # value: param_value
30
+
31
+ # Global variables
32
+ variables:
33
+ - name: tweet
34
+ value: 'Checking out resat (http://tinyurl.com/dg8gf9)'
35
+
36
+ # Input file
37
+ # input: input.yml
38
+
39
+ # Output file
40
+ output: output.yml
@@ -0,0 +1,5 @@
1
+ ---
2
+ - name: last_public_tweet
3
+ value: "Novellus&amp;#39; CoolFill CVD Process Advances Tungsten Fill for Sub-32nm ...: Logic devices, though not as aggress.. http://tinyurl.com/csja8w"
4
+ - name: last_friends_tweet
5
+ value: "@rgsimon Hey resat is cool!"
@@ -0,0 +1,31 @@
1
+ # Retrieve public and private recent tweets
2
+ #
3
+ # http://twitter.com/statuses/public_timeline.format
4
+ # http://twitter.com/statuses/public_timeline.format
5
+ name: Twitter Timelines
6
+ config: ../config/resat.yaml
7
+ steps:
8
+ - request:
9
+ resource: statuses
10
+ custom: # Use a custom operation (i.e. not a CRUD operation)
11
+ name: public_timeline.xml # Operation name
12
+ type: get # GET request
13
+ filters:
14
+ - name: extract latest public tweet
15
+ target: body
16
+ extractors:
17
+ - field: statuses/status/text
18
+ variable: last public tweet
19
+ save: yes
20
+ - request:
21
+ resource: statuses
22
+ custom: # Use a custom operation (i.e. not a CRUD operation)
23
+ name: friends_timeline.xml # Operation name
24
+ type: get # GET request
25
+ filters:
26
+ - name: extract latest friends tweet
27
+ target: body
28
+ extractors:
29
+ - field: statuses/status/text
30
+ variable: last friends tweet
31
+ save: yes
@@ -0,0 +1,14 @@
1
+ # Send Tweet
2
+ # http://twitter.com/statuses/update.format
3
+ name: Send Tweet
4
+ config: ../config/resat.yaml
5
+ steps:
6
+ - request:
7
+ resource: statuses # Act on the 'notifications' resource
8
+ custom: # Use a custom operation (i.e. not a CRUD operation)
9
+ name: update.xml # Operation name
10
+ type: post # POST request
11
+ params:
12
+ - name: status
13
+ value: $tweet
14
+
@@ -0,0 +1,145 @@
1
+ # API request
2
+ # See resat.rb for usage information.
3
+ #
4
+
5
+ require 'uri'
6
+ require 'rexml/document'
7
+ require File.join(File.dirname(__FILE__), 'net_patch')
8
+
9
+ module Resat
10
+
11
+ class ApiRequest
12
+ include Kwalify::Util::HashLike
13
+ attr_reader :request, :response, :send_count, :failures
14
+
15
+ # Prepare request so 'send' can be called
16
+ def prepare
17
+ @format ||= 'xml'
18
+ @failures = []
19
+ @send_count = 0
20
+
21
+ # 1. Normalize call fields
22
+ @headers ||= []
23
+ @params ||= []
24
+ # Clone config values so we don't mess with them when expanding variables
25
+ Config.headers.each do |h|
26
+ @headers << { 'name' => h['name'].dup, 'value' => h['value'].dup }
27
+ end if Config.headers
28
+ Config.params.each do |p|
29
+ @params << { 'name' => p['name'].dup, 'value' => p['value'].dup }
30
+ end if Config.params && request_class.REQUEST_HAS_BODY
31
+ Variables.substitute!(@params)
32
+ Variables.substitute!(@headers)
33
+
34
+ # 2. Build URI
35
+ Variables.substitute!(@id) if @id
36
+ uri_class = (@use_ssl || @use_ssl.nil? && Config.use_ssl) ? URI::HTTPS : URI::HTTP
37
+ port = @port || Config.port || uri_class::DEFAULT_PORT
38
+ @uri = uri_class.build( :host => @host || Config.host, :port => port )
39
+ base_url = "/#{@base_url || Config.base_url}/".squeeze('/')
40
+ Variables.substitute!(base_url)
41
+ path = "#{base_url}#{@resource}"
42
+ path = "#{path}/#{@id}" if @id
43
+ path = "#{path}.#{@format}" if @format && !@custom
44
+ path = "#{path}#{@custom.separator}#{@custom.name}" if @custom
45
+ @uri.merge!(path)
46
+
47
+ # 3. Build request
48
+ case @operation
49
+ when 'index', 'show' then request_class = Net::HTTP::Get
50
+ when 'create' then request_class = Net::HTTP::Post
51
+ when 'update' then request_class = Net::HTTP::Put
52
+ when 'destroy' then request_class = Net::HTTP::Delete
53
+ else
54
+ if @custom
55
+ case @custom.type
56
+ when 'get' then request_class = Net::HTTP::Get
57
+ when 'post' then request_class = Net::HTTP::Post
58
+ when 'put' then request_class = Net::HTTP::Put
59
+ when 'delete' then request_class = Net::HTTP::Delete
60
+ end
61
+ else
62
+ @failures << "Missing request operation for request on '#{@resource}'."
63
+ return
64
+ end
65
+ end
66
+ @request = request_class.new(@uri.to_s)
67
+ username = @username || Config.username
68
+ Variables.substitute!(username) if username
69
+ password = @password || Config.password
70
+ Variables.substitute!(password) if password
71
+ if username && password
72
+ @request.basic_auth(username, password)
73
+ end
74
+ form_data = Hash.new
75
+ @headers.each { |header| @request[header['name']] = header['value'] }
76
+ @params.each { |param| form_data[param['name']] = param['value'] }
77
+ @request.set_form_data(form_data) unless form_data.empty?
78
+ Log.request(@request)
79
+
80
+ # 4. Send request and check response code
81
+ @oks = @valid_codes.map { |r| r.to_s } if @valid_codes
82
+ @oks ||= %w{200 201 202 203 204 205 206}
83
+ end
84
+
85
+ # Actually send the request
86
+ def send
87
+ sleep(delay_seconds) # Delay request if needed
88
+ http = Net::HTTP.new(@uri.host, @uri.port)
89
+ http.use_ssl = Config.use_ssl
90
+ begin
91
+ res = http.start { |http| @response = http.request(@request) }
92
+ rescue Exception => e
93
+ @failures << "Exception raised while making request: #{e.message}"
94
+ end
95
+ if @failures.size == 0
96
+ @send_count += 1
97
+ if @oks.include?(res.code)
98
+ Log.response(@response)
99
+ else
100
+ Log.response(@response, false)
101
+ @failures << "Request returned #{res.code}"
102
+ end
103
+ end
104
+ end
105
+
106
+ # Does response include given header or body field?
107
+ def has_response_field?(field, target)
108
+ return unless @response
109
+ return @response.key?(field) if target == 'header'
110
+ doc = REXML::Document.new(@response.body) rescue nil
111
+ return doc && !doc.elements[field].nil?
112
+ end
113
+
114
+ # Get value of response header or body field
115
+ def get_response_field(field, target)
116
+ return unless @response
117
+ return @response[field] if target == 'header'
118
+ doc = REXML::Document.new(@response.body)
119
+ elem = doc.elements[field]
120
+ return elem.get_text.to_s if elem
121
+ end
122
+
123
+ protected
124
+
125
+ # Calculate number of seconds to wait before sending request
126
+ def delay_seconds
127
+ seconds = nil
128
+ if delay = @delay || Config.delay
129
+ min_delay = max_delay = nil
130
+ if delay =~ /([\d]+)\.\.([\d]+)/
131
+ min_delay = Regexp.last_match[1].to_i
132
+ max_delay = Regexp.last_match[2].to_i
133
+ elsif delay.to_i.to_s == delay
134
+ min_delay = max_delay = delay.to_i
135
+ end
136
+ if min_delay && max_delay
137
+ seconds = rand(max_delay - min_delay + 1) + min_delay
138
+ end
139
+ end
140
+ seconds || 0
141
+ end
142
+
143
+ end
144
+
145
+ end