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.
- data/LICENSE +20 -0
- data/README.rdoc +321 -0
- data/Rakefile +33 -0
- data/bin/resat +223 -0
- data/examples/rightscale/README.rdoc +39 -0
- data/examples/rightscale/additional/run_server.yml +75 -0
- data/examples/rightscale/config/resat.yaml +34 -0
- data/examples/rightscale/scenarios/create_server.yml +48 -0
- data/examples/rightscale/scenarios/delete_server.yml +13 -0
- data/examples/rightscale/scenarios/list_servers.yml +9 -0
- data/examples/twitter/README.rdoc +50 -0
- data/examples/twitter/additional/follow.yml +15 -0
- data/examples/twitter/additional/send_message.yml +19 -0
- data/examples/twitter/config/resat.yaml +40 -0
- data/examples/twitter/output.yml +5 -0
- data/examples/twitter/scenarios/timelines.yml +31 -0
- data/examples/twitter/scenarios/tweet.yml +14 -0
- data/lib/api_request.rb +145 -0
- data/lib/config.rb +94 -0
- data/lib/engine.rb +98 -0
- data/lib/file_set.rb +33 -0
- data/lib/filter.rb +113 -0
- data/lib/guard.rb +36 -0
- data/lib/handler.rb +50 -0
- data/lib/kwalify_helper.rb +31 -0
- data/lib/log.rb +114 -0
- data/lib/net_patch.rb +15 -0
- data/lib/rdoc_patch.rb +37 -0
- data/lib/resat.rb +5 -0
- data/lib/scenario_runner.rb +203 -0
- data/lib/variables.rb +116 -0
- data/schemas/config.yaml +48 -0
- data/schemas/scenarios.yaml +169 -0
- data/schemas/variables.yaml +18 -0
- metadata +98 -0
@@ -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,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,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
|
+
|
data/lib/api_request.rb
ADDED
@@ -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
|