rscalr 0.0.12 → 0.0.15

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/README.md CHANGED
@@ -43,6 +43,24 @@ script = dashboard.get_script 'my-script-name'
43
43
  script.execute farm.id
44
44
  ```
45
45
 
46
+ Configuration
47
+ -------------
48
+
49
+ If you use this tool a lot from the command line, you can set environment variables that will automagically configure your Scalr or Dashboard instance. The following code sets the relevant parameters:
50
+ ```bash
51
+ export SCALR_API_KEY=your-api-key
52
+ export SCALR_API_SECRET=your-api-secret
53
+ export SCALR_ENV_ID=id-of-desired-environment # optional
54
+ export SCALR_CLIENT_VERBOSE=true # Print API calls/responses to stdout, optional, default=false
55
+ ```
56
+
57
+ Then just instaniate your client:
58
+ ```ruby
59
+ scalr = Scalr.new
60
+ # or
61
+ dash = Dashboard.new
62
+ ```
63
+
46
64
 
47
65
  Caveats
48
66
  -------
@@ -7,4 +7,7 @@ require 'rscalr/model/log'
7
7
  require 'rscalr/model/log_list'
8
8
  require 'rscalr/model/role'
9
9
  require 'rscalr/model/script'
10
+ require 'rscalr/model/script_log'
11
+ require 'rscalr/model/script_execution'
10
12
  require 'rscalr/model/server'
13
+ require 'rscalr/model/server_log'
@@ -16,8 +16,13 @@ class Scalr
16
16
  $DEFAULT_VERSION = '2.3.0'
17
17
  $DEFAULT_AUTH_VERSION = '3'
18
18
 
19
- def initialize(config)
20
- @config = config
19
+ def initialize(config=nil)
20
+ if (config.nil?)
21
+ @config = load_config_from_environment
22
+ else
23
+ @config = config
24
+ end
25
+
21
26
  @config[:version] = $DEFAULT_VERSION if @config[:version].nil?
22
27
  @config[:auth_version] = $DEFAULT_AUTH_VERSION if @config[:auth_version].nil?
23
28
  end
@@ -164,7 +169,7 @@ class Scalr
164
169
  params[:Prefix] = prefix unless prefix.nil?
165
170
  params[:ImageID] = image_id unless image_id.nil?
166
171
 
167
- execute_api_call('RolesList', params)
172
+ execute_api_call('RolesList', params)
168
173
  end
169
174
 
170
175
  def scripts_list
@@ -253,6 +258,18 @@ class Scalr
253
258
 
254
259
  #=============== Helper methods ==================================
255
260
 
261
+ # Loads config hash based on environment variables
262
+ def load_config_from_environment
263
+ config = {}
264
+ config[:key_id] = ENV['SCALR_API_KEY'] unless ENV['SCALR_API_KEY'].nil?
265
+ config[:key_secret] = ENV['SCALR_API_SECRET'] unless ENV['SCALR_API_SECRET'].nil?
266
+ config[:env_id] = ENV['SCALR_ENV_ID'] unless ENV['SCALR_ENV_ID'].nil?
267
+ config[:version] = ENV['SCALR_API_VERSION'] unless ENV['SCALR_API_VERSION'].nil?
268
+ config[:auth_version] = ENV['SCALR_API_AUTH_VERSION'] unless ENV['SCALR_API_AUTH_VERSION'].nil?
269
+ config[:verbose] = true if (ENV['SCALR_CLIENT_VERBOSE'] == "true" || ENV['SCALR_CLIENT_VERBOSE'] == "1")
270
+ config
271
+ end
272
+
256
273
  # Generates request signature based on config, action, timestamp
257
274
  def generate_sig(action, timestamp)
258
275
  message = action + ':' + @config[:key_id] + ':' + timestamp
@@ -297,6 +314,8 @@ class Scalr
297
314
  result = build_error_response(ex.message, params[:Signature])
298
315
  end
299
316
 
317
+ $stdout.puts(result.pretty_print) if @config[:verbose]
318
+
300
319
  result
301
320
  end
302
321
 
@@ -362,7 +381,9 @@ class ScalrResponse < REXML::Document
362
381
 
363
382
  # Convenience method to output a repsonse to a stream in a human readable format
364
383
  def pretty_print(dest=$stdout)
365
- write(dest, 1)
384
+ formatter = REXML::Formatters::Pretty.new
385
+ formatter.compact = true
386
+ formatter.write(root,dest)
366
387
  end
367
388
  end
368
389
 
@@ -1,12 +1,12 @@
1
1
  class Dashboard
2
2
 
3
- def initialize config
3
+ def initialize(config=nil)
4
4
  if config.is_a?(Scalr)
5
5
  @client = config
6
- elsif config.is_a?(Hash)
6
+ elsif config.nil? || config.is_a?(Hash)
7
7
  @client = Scalr.new(config)
8
8
  else
9
- raise 'Dashboard may only be initialized with a config Hash or Scalr object'
9
+ raise 'Dashboard may only be initialized with a config Hash, Scalr object, or nil (load config from environment)'
10
10
  end
11
11
 
12
12
  @env_id = @client.env_id
@@ -19,6 +19,17 @@ class Dashboard
19
19
  @farms[name]
20
20
  end
21
21
 
22
+ def get_farm_by_id(farm_id)
23
+ if @farms.nil?
24
+ load_farms
25
+ end
26
+ @farms.each { |name,farm|
27
+ return farm if farm.id == farm_id
28
+ }
29
+
30
+ nil
31
+ end
32
+
22
33
  def load_farms
23
34
  @farms = {}
24
35
 
@@ -98,4 +109,57 @@ class Dashboard
98
109
 
99
110
  @environments
100
111
  end
112
+
113
+ # Blocks up to timeout seconds waiting on log response(s) of script execution. Returns true iff
114
+ # script executed successfully (returned) exit_value on all servers.
115
+ def verify_script_success(script_execution, exit_value=0, timeout_sec=60)
116
+ return false unless script_execution.success?
117
+
118
+ sleep_time = 5
119
+
120
+ start_time = Time.now.to_i
121
+
122
+ # 1. get list of servers that this execution should run on
123
+ farm = get_farm_by_id(script_execution.farm_id)
124
+ if !script_execution.server_id.nil?
125
+ server = farm.get_server(script_execution.server_id)
126
+ script_execution.add_server(server.server_id)
127
+ elsif !script_execution.farm_role_id.nil?
128
+ role_servers = farm.get_servers_for_role_id(script_execution.farm_role_id)
129
+ role_servers.each { |server|
130
+ script_execution.add_server(server.server_id)
131
+ }
132
+ else
133
+ farm_servers = farm.get_all_servers
134
+ farm_servers.each { |server|
135
+ script_execution.add_server(server.server_id)
136
+ }
137
+ end
138
+
139
+ # 2. Call logs list and match results to server IDs
140
+ success = false
141
+ finished = false
142
+ begin
143
+ start = 0
144
+ begin
145
+ loglist = farm.load_script_logs(script_execution.server_id, script_execution.event_id, start, 20)
146
+ total_records = loglist.total_records
147
+
148
+ loglist.each { |log|
149
+ script_execution.set_server_result(log)
150
+ start += 1
151
+ }
152
+ end while start < total_records
153
+
154
+ # 3. If not all servers have responses logged, repeat Step 2 until done or timeout_sec seconds have expired
155
+ success = true
156
+ finished = true
157
+ script_execution.server_results.each { |server_id, result|
158
+ success &&= (!result.nil? && result.exec_exit_code == exit_value)
159
+ finished &&= !result.nil?
160
+ }
161
+ end while !finished && (Time.now.to_i - start_time) < (timeout_sec + sleep_time) && sleep(sleep_time)
162
+
163
+ success
164
+ end
101
165
  end
@@ -12,8 +12,40 @@ class Farm
12
12
  @farm_roles[name]
13
13
  end
14
14
 
15
+ def get_server(name)
16
+ if @farm_servers.nil?
17
+ load_details
18
+ end
19
+ @farm_servers[name]
20
+ end
21
+
22
+ def get_servers_for_role_id(farm_role_id)
23
+ if @farm_roles.nil?
24
+ load_details
25
+ end
26
+ result = []
27
+ @farm_roles.each { |name,role|
28
+ result = role.servers.dup if role.farm_role_id == farm_role_id
29
+ }
30
+ result
31
+ end
32
+
33
+ def get_all_servers
34
+ if @farm_roles.nil?
35
+ load_details
36
+ end
37
+ result = []
38
+ @farm_roles.each { |name,role|
39
+ role.servers.each { |server|
40
+ result << server
41
+ }
42
+ }
43
+ result
44
+ end
45
+
15
46
  def load_details
16
47
  @farm_roles = {}
48
+ @farm_servers = {}
17
49
 
18
50
  scalr_response = @client.farm_get_details @id
19
51
  scalr_response.root.each_element('FarmRoleSet/Item') { |row|
@@ -21,9 +53,9 @@ class Farm
21
53
 
22
54
  row.each_element { |prop|
23
55
  if "ID" == prop.name
24
- role.farm_role_id = prop.text
56
+ role.farm_role_id = prop.text.to_i
25
57
  elsif "RoleID" == prop.name
26
- role.role_id = prop.text
58
+ role.role_id = prop.text.to_i
27
59
  elsif "Name" == prop.name
28
60
  role.name = prop.text
29
61
  elsif "ServerSet" == prop.name
@@ -32,21 +64,22 @@ class Farm
32
64
  server.role = role
33
65
 
34
66
  server_element.each_element { |server_prop|
35
- if "ServerID" == prop.name
36
- server.server_id = prop.text
37
- elsif "ExternalIP" == prop.name
38
- server.external_ip = prop.text
39
- elsif "InternalIP" == prop.name
40
- server.external_ip = prop.text
41
- elsif "Status" == prop.name
42
- server.status = prop.text
43
- elsif "Index" == prop.name
44
- server.index = prop.text.to_i
45
- elsif "Uptime" == prop.name
46
- server.uptime = prop.text.to_f
67
+ if "ServerID" == server_prop.name
68
+ server.server_id = server_prop.text
69
+ elsif "ExternalIP" == server_prop.name
70
+ server.external_ip = server_prop.text
71
+ elsif "InternalIP" == server_prop.name
72
+ server.external_ip = server_prop.text
73
+ elsif "Status" == server_prop.name
74
+ server.status = server_prop.text
75
+ elsif "Index" == server_prop.name
76
+ server.index = server_prop.text.to_i
77
+ elsif "Uptime" == server_prop.name
78
+ server.uptime = server_prop.text.to_f
47
79
  end
48
80
  }
49
81
  role.servers << server
82
+ @farm_servers[server.server_id] = server
50
83
  }
51
84
  end
52
85
  }
@@ -71,7 +104,7 @@ class Farm
71
104
  loglist.limit = element.text.to_i
72
105
  elsif "LogSet" == element.name
73
106
  element.each_element do |item|
74
- log = Log.new
107
+ log = ServerLog.new
75
108
 
76
109
  item.each_element do |prop|
77
110
  if "ServerID" == prop.name
@@ -83,7 +116,48 @@ class Farm
83
116
  elsif "Timestamp" == prop.name
84
117
  log.timestamp = prop.text.to_i
85
118
  elsif "Source" == prop.name
86
- server.source = prop.text
119
+ log.source = prop.text
120
+ end
121
+ end
122
+ loglist << log
123
+ end
124
+ end
125
+ end
126
+
127
+ loglist
128
+ end
129
+
130
+ # Loads script execution logs for the farm, or a particular server in the farm, optionally relating to a particular script execution.
131
+ def load_script_logs(server_id=nil, event_id=nil, start=nil, limit=nil)
132
+ scalr_response = @client.scripting_logs_list(@id, server_id, event_id, start, limit)
133
+ loglist = LogList.new
134
+
135
+ scalr_response.root.each_element do |element|
136
+ if "TotalRecords" == element.name
137
+ loglist.total_records = element.text.to_i
138
+ elsif "StartFrom" == element.name
139
+ loglist.start = element.text.to_i
140
+ elsif "RecordsLimit" == element.name
141
+ loglist.limit = element.text.to_i
142
+ elsif "LogSet" == element.name
143
+ element.each_element do |item|
144
+ log = ScriptLog.new
145
+
146
+ item.each_element do |prop|
147
+ if "ServerID" == prop.name
148
+ log.server_id = prop.text
149
+ elsif "Message" == prop.name
150
+ log.message = prop.text
151
+ elsif "ScriptName" == prop.name
152
+ log.script_name = prop.text
153
+ elsif "Timestamp" == prop.name
154
+ log.timestamp = prop.text.to_i
155
+ elsif "ExecTime" == prop.name
156
+ log.exec_time = prop.text.to_i
157
+ elsif "ExecExitCode" == prop.name
158
+ log.exec_exit_code = prop.text.to_i
159
+ elsif "Event" == prop.name
160
+ log.event = prop.text
87
161
  end
88
162
  end
89
163
  loglist << log
@@ -1,7 +1,7 @@
1
1
  class Log
2
- attr_accessor :server_id, :message, :severity, :timestamp, :source
2
+ attr_accessor :server_id, :message, :timestamp
3
3
 
4
4
  def to_s
5
- "{ type: \"log\", server_id: \"#{@server_id}\", message: \"#{@message}\", severity: #{@severity}, timestamp: #{@timestamp}, source: \"#{@source}\" }"
5
+ "{ type: \"log\", server_id: \"#{@server_id}\", message: \"#{@message}\", timestamp: #{@timestamp} }"
6
6
  end
7
7
  end
@@ -10,6 +10,12 @@ class LogList
10
10
  @logs << log if log.is_a? Log
11
11
  end
12
12
 
13
+ def each
14
+ @logs.each { |log|
15
+ yield log
16
+ }
17
+ end
18
+
13
19
  def to_s
14
20
  "{ type: \"loglist\", total_records: #{@total_records}, start: \"#{@message}\", limit: #{@severity}, logs: [ #{@logs.each do |log| log.to_s + ',' end} ] }"
15
21
  end
@@ -9,7 +9,8 @@ class Script
9
9
 
10
10
  def execute(farm_id, timeout=30, async=:no_async, farm_role_id=nil, server_id=nil, revision=nil, config_vars=nil)
11
11
  set_revision(revision) # Side effect!
12
- @client.script_execute farm_id, @id, timeout, async, farm_role_id, server_id, @revision, config_vars
12
+ api_response = @client.script_execute farm_id, @id, timeout, async, farm_role_id, server_id, @revision, config_vars
13
+ parse_script_execution_response(api_response, farm_id, farm_role_id, server_id)
13
14
  end
14
15
 
15
16
  def load_details
@@ -57,6 +58,23 @@ class Script
57
58
  end
58
59
  end
59
60
 
61
+ def parse_script_execution_response(api_response, farm_id, farm_role_id=nil, server_id=nil)
62
+ if api_response.success?
63
+ result = nil
64
+ event_id = nil
65
+ api_response.root.each_element { |field|
66
+ if "TransactionID" == field.name
67
+ event_id = field.text
68
+ elsif "Result" == field.name
69
+ result = field.text.to_i
70
+ end
71
+ }
72
+ ScriptExecution.new(@id, result, event_id, farm_id, farm_role_id, server_id)
73
+ elsif
74
+ ScriptExecution.new(@id, 0, nil, farm_id)
75
+ end
76
+ end
77
+
60
78
  def to_s
61
79
  "{ type: \"script\", id: #{@id}, name: \"#{@name}\" }"
62
80
  end
@@ -0,0 +1,45 @@
1
+ class ScriptExecution
2
+ attr_reader :script_id, :result, :event_id, :farm_id, :farm_role_id, :server_id, :server_results
3
+
4
+ def initialize(script_id, result, event_id, farm_id, farm_role_id=nil, server_id=nil)
5
+ @script_id = script_id
6
+ @result = result
7
+ @result = 0 if @result != 1
8
+ @event_id = event_id
9
+ @farm_id = farm_id
10
+ @farm_role_id = farm_role_id
11
+ @server_id = server_id
12
+
13
+ # instance field to store execution results
14
+ @server_results = {}
15
+ end
16
+
17
+ def success?
18
+ @result == 1
19
+ end
20
+
21
+ def add_server(server_id)
22
+ @server_results[server_id] = nil
23
+ end
24
+
25
+ def set_server_result(log)
26
+ @server_results[log.server_id] = log if @server_results.has_key?(log.server_id)
27
+ end
28
+
29
+ def to_s
30
+ "{ type: \"script_execution\", script_id: #{@script_id}, result: #{@result}, event_id: \"#{@event_id}\"}"
31
+ end
32
+ end
33
+
34
+ class ScriptExecutionServerResult
35
+ attr_accessor :script_id, :server_id, :log
36
+
37
+ def initialize(script_id, server_id)
38
+ @script_id = script_id
39
+ @server_id = server_id
40
+ end
41
+
42
+ def to_s
43
+ "{ type: \"script_execution_server_result\", script_id: #{@script_id}, server_id: \"#{@server_id}\", result: #{@log} }"
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ class ScriptLog < Log
2
+ attr_accessor :script_name, :exec_time, :exec_exit_code, :event
3
+
4
+ def to_s
5
+ "{ type: \"script_log\", server_id: \"#{@server_id}\", script_name: \"#{@script_name}\", message: \"#{@message}\", timestamp: #{@timestamp}, exec_exit_code: #{@exec_exit_code} }"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class ServerLog < Log
2
+ attr_accessor :severity, :source
3
+
4
+ def to_s
5
+ "{ type: \"server_log\", server_id: \"#{@server_id}\", message: \"#{@message}\", timestamp: #{@timestamp}, severity: #{@severity}, source: \"#{@source}\" }"
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module Rscalr
2
- VERSION = "0.0.12"
2
+ VERSION = "0.0.15"
3
3
  end
@@ -22,9 +22,7 @@ class TestObjLogs < Test::Unit::TestCase
22
22
 
23
23
  loglist = farm.load_logs
24
24
 
25
- assert_not_nil(loglist, "Log list could not be loaded")
26
- assert_equal(0, loglist.total_records, "Test farm 1 should not have any logs associated with it")
27
-
25
+ assert_not_nil(loglist, "Log list could not be loaded")
28
26
  end
29
27
 
30
28
  end
metadata CHANGED
@@ -1,25 +1,28 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rscalr
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.12
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.0.15
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Nathan Smith
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-11 00:00:00.000000000 -07:00
13
- default_executable:
12
+
13
+ date: 2013-03-14 00:00:00 Z
14
14
  dependencies: []
15
- description: Rscalr is a Ruby implementation of the Scalr API, written to interface
16
- cleanly with Chef and other internal release management tasks.
17
- email:
15
+
16
+ description: Rscalr is a Ruby implementation of the Scalr API, written to interface cleanly with Chef and other internal release management tasks.
17
+ email:
18
18
  - nate@branchout.com
19
19
  executables: []
20
+
20
21
  extensions: []
22
+
21
23
  extra_rdoc_files: []
22
- files:
24
+
25
+ files:
23
26
  - .gitignore
24
27
  - README.md
25
28
  - lib/rscalr.rb
@@ -32,7 +35,10 @@ files:
32
35
  - lib/rscalr/model/log_list.rb
33
36
  - lib/rscalr/model/role.rb
34
37
  - lib/rscalr/model/script.rb
38
+ - lib/rscalr/model/script_execution.rb
39
+ - lib/rscalr/model/script_log.rb
35
40
  - lib/rscalr/model/server.rb
41
+ - lib/rscalr/model/server_log.rb
36
42
  - lib/rscalr/version.rb
37
43
  - rscalr.gemspec
38
44
  - test/test_env_id.rb
@@ -40,32 +46,34 @@ files:
40
46
  - test_integration/test_client_farms.rb
41
47
  - test_integration/test_obj_environments.rb
42
48
  - test_integration/test_obj_logs.rb
43
- has_rdoc: true
44
49
  homepage:
45
50
  licenses: []
51
+
46
52
  post_install_message:
47
53
  rdoc_options: []
48
- require_paths:
54
+
55
+ require_paths:
49
56
  - lib
50
- required_ruby_version: !ruby/object:Gem::Requirement
57
+ required_ruby_version: !ruby/object:Gem::Requirement
51
58
  none: false
52
- requirements:
53
- - - ! '>='
54
- - !ruby/object:Gem::Version
55
- version: '0'
56
- required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
64
  none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
62
69
  requirements: []
70
+
63
71
  rubyforge_project:
64
- rubygems_version: 1.6.2
72
+ rubygems_version: 1.8.25
65
73
  signing_key:
66
74
  specification_version: 3
67
75
  summary: Ruby implementation of the Scalr API
68
- test_files:
76
+ test_files:
69
77
  - test/test_env_id.rb
70
78
  - test/test_sig.rb
71
79
  - test_integration/test_client_farms.rb