right_agent 2.0.7-x86-mingw32
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 +82 -0
- data/Rakefile +113 -0
- data/lib/right_agent.rb +59 -0
- data/lib/right_agent/actor.rb +182 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +232 -0
- data/lib/right_agent/agent.rb +1149 -0
- data/lib/right_agent/agent_config.rb +480 -0
- data/lib/right_agent/agent_identity.rb +210 -0
- data/lib/right_agent/agent_tag_manager.rb +237 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/clients.rb +31 -0
- data/lib/right_agent/clients/api_client.rb +383 -0
- data/lib/right_agent/clients/auth_client.rb +247 -0
- data/lib/right_agent/clients/balanced_http_client.rb +369 -0
- data/lib/right_agent/clients/base_retry_client.rb +495 -0
- data/lib/right_agent/clients/right_http_client.rb +279 -0
- data/lib/right_agent/clients/router_client.rb +493 -0
- data/lib/right_agent/command.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +150 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +33 -0
- data/lib/right_agent/command/command_io.rb +126 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +118 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +44 -0
- data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
- data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
- data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
- data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
- data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
- data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +79 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
- data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
- data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
- data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
- data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
- data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
- data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
- data/lib/right_agent/daemonize.rb +35 -0
- data/lib/right_agent/dispatched_cache.rb +109 -0
- data/lib/right_agent/dispatcher.rb +272 -0
- data/lib/right_agent/enrollment_result.rb +221 -0
- data/lib/right_agent/exceptions.rb +87 -0
- data/lib/right_agent/history.rb +145 -0
- data/lib/right_agent/log.rb +460 -0
- data/lib/right_agent/minimal.rb +46 -0
- data/lib/right_agent/monkey_patches.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
- data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
- data/lib/right_agent/multiplexer.rb +102 -0
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/operation_result.rb +300 -0
- data/lib/right_agent/packets.rb +673 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +770 -0
- data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
- data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
- data/lib/right_agent/platform/unix/platform.rb +226 -0
- data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
- data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
- data/lib/right_agent/platform/windows/platform.rb +1808 -0
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/retryable_request.rb +195 -0
- data/lib/right_agent/scripts/agent_controller.rb +543 -0
- data/lib/right_agent/scripts/agent_deployer.rb +400 -0
- data/lib/right_agent/scripts/common_parser.rb +160 -0
- data/lib/right_agent/scripts/log_level_manager.rb +192 -0
- data/lib/right_agent/scripts/stats_manager.rb +268 -0
- data/lib/right_agent/scripts/usage.rb +58 -0
- data/lib/right_agent/secure_identity.rb +92 -0
- data/lib/right_agent/security.rb +32 -0
- data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
- data/lib/right_agent/security/certificate.rb +102 -0
- data/lib/right_agent/security/certificate_cache.rb +89 -0
- data/lib/right_agent/security/distinguished_name.rb +56 -0
- data/lib/right_agent/security/encrypted_document.rb +83 -0
- data/lib/right_agent/security/rsa_key_pair.rb +76 -0
- data/lib/right_agent/security/signature.rb +86 -0
- data/lib/right_agent/security/static_certificate_store.rb +85 -0
- data/lib/right_agent/sender.rb +792 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +107 -0
- data/lib/right_agent/serialize/secure_serializer.rb +151 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +151 -0
- data/lib/right_agent/serialize/serializer.rb +159 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +101 -0
- data/spec/actor_registry_spec.rb +80 -0
- data/spec/actor_spec.rb +162 -0
- data/spec/agent_config_spec.rb +235 -0
- data/spec/agent_identity_spec.rb +78 -0
- data/spec/agent_spec.rb +734 -0
- data/spec/agent_tag_manager_spec.rb +319 -0
- data/spec/clients/api_client_spec.rb +423 -0
- data/spec/clients/auth_client_spec.rb +272 -0
- data/spec/clients/balanced_http_client_spec.rb +576 -0
- data/spec/clients/base_retry_client_spec.rb +635 -0
- data/spec/clients/router_client_spec.rb +594 -0
- data/spec/clients/spec_helper.rb +111 -0
- data/spec/command/agent_manager_commands_spec.rb +51 -0
- data/spec/command/command_io_spec.rb +93 -0
- data/spec/command/command_parser_spec.rb +79 -0
- data/spec/command/command_runner_spec.rb +107 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/dev_repository_spec.rb +33 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
- data/spec/core_payload_types/login_user_spec.rb +102 -0
- data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatched_cache_spec.rb +136 -0
- data/spec/dispatcher_spec.rb +324 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/history_spec.rb +246 -0
- data/spec/log_spec.rb +192 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/operation_result_spec.rb +208 -0
- data/spec/packets_spec.rb +461 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/platform/spec_helper.rb +216 -0
- data/spec/platform/unix/darwin/platform_spec.rb +181 -0
- data/spec/platform/unix/linux/platform_spec.rb +540 -0
- data/spec/platform/unix/spec_helper.rb +149 -0
- data/spec/platform/windows/mingw/platform_spec.rb +222 -0
- data/spec/platform/windows/mswin/platform_spec.rb +259 -0
- data/spec/platform/windows/spec_helper.rb +720 -0
- data/spec/retryable_request_spec.rb +306 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
- data/spec/security/certificate_cache_spec.rb +71 -0
- data/spec/security/certificate_spec.rb +49 -0
- data/spec/security/distinguished_name_spec.rb +46 -0
- data/spec/security/encrypted_document_spec.rb +55 -0
- data/spec/security/rsa_key_pair_spec.rb +55 -0
- data/spec/security/signature_spec.rb +66 -0
- data/spec/security/static_certificate_store_spec.rb +58 -0
- data/spec/sender_spec.rb +1045 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +132 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +197 -0
- data/spec/spec.opts +2 -0
- data/spec/spec.win32.opts +1 -0
- data/spec/spec_helper.rb +130 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +447 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
CLIENTS_BASE_DIR = File.join(File.dirname(__FILE__), 'clients')
|
25
|
+
|
26
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'balanced_http_client'))
|
27
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'base_retry_client'))
|
28
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'auth_client'))
|
29
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'api_client'))
|
30
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'router_client'))
|
31
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'right_http_client'))
|
@@ -0,0 +1,383 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
|
26
|
+
# HTTP interface to RightApi for use when mapping actor-based requests to API requests
|
27
|
+
class ApiClient < BaseRetryClient
|
28
|
+
|
29
|
+
# RightApi API version for use in X-API-Version header
|
30
|
+
API_VERSION = "1.5"
|
31
|
+
|
32
|
+
# Maximum length of an audit summary as enforced by RightApi
|
33
|
+
MAX_AUDIT_SUMMARY_LENGTH = 255
|
34
|
+
|
35
|
+
# Default time to wait for HTTP connection to open
|
36
|
+
DEFAULT_OPEN_TIMEOUT = 2
|
37
|
+
|
38
|
+
# Default time to wait for response from request, which is chosen to be 5 seconds greater
|
39
|
+
# than the response timeout inside the RightNet router
|
40
|
+
DEFAULT_REQUEST_TIMEOUT = 35
|
41
|
+
|
42
|
+
# Map from actor-based request paths to RightApi HTTP verb and path; only requests whose type
|
43
|
+
# matches an entry in this hash will be routed to the RightApi; all others will be routed to RightNet
|
44
|
+
API_MAP = {
|
45
|
+
"/auditor/create_entry" => [:post, "/audit_entries"],
|
46
|
+
"/auditor/update_entry" => [:post, "/audit_entries/:id/append"],
|
47
|
+
"/booter/declare" => [:post, "/right_net/booter/declare"],
|
48
|
+
"/booter/get_repositories" => [:get, "/right_net/booter/get_repositories"],
|
49
|
+
"/booter/get_boot_bundle" => [:get, "/right_net/booter/get_boot_bundle"],
|
50
|
+
"/booter/get_decommission_bundle" => [:get, "/right_net/booter/get_decommission_bundle"],
|
51
|
+
"/booter/get_missing_attributes" => [:get, "/right_net/booter/get_missing_attributes"],
|
52
|
+
"/booter/get_login_policy" => [:get, "/right_net/booter/get_login_policy"],
|
53
|
+
"/forwarder/schedule_right_script" => [:post, "/right_net/scheduler/bundle_right_script"],
|
54
|
+
"/forwarder/schedule_recipe" => [:post, "/right_net/scheduler/bundle_recipe"],
|
55
|
+
"/forwarder/shutdown" => [:post, "/right_net/scheduler/shutdown"],
|
56
|
+
"/key_server/retrieve_public_keys" => [:get, "/right_net/key_server/retrieve_public_keys"],
|
57
|
+
"/router/ping" => [:get, "/health-check"],
|
58
|
+
"/router/query_tags" => [:post, "/tags/by_tag"],
|
59
|
+
"/router/add_tags" => [:post, "/tags/multi_add"],
|
60
|
+
"/router/delete_tags" => [:post, "/tags/multi_delete"],
|
61
|
+
"/state_recorder/record" => [:put, "/right_net/state_recorder/record"],
|
62
|
+
"/storage_valet/get_planned_volumes" => [:get, "/right_net/storage_valet/get_planned_volumes"],
|
63
|
+
"/storage_valet/attach_volume" => [:post, "/right_net/storage_valet/attach_volume"],
|
64
|
+
"/storage_valet/detach_volume" => [:post, "/right_net/storage_valet/detach_volume"],
|
65
|
+
"/updater/update_inputs" => [:post, "/right_net/scheduler/update_inputs"],
|
66
|
+
"/vault/read_documents" => [:get, "/right_net/vault/read_documents"] }
|
67
|
+
|
68
|
+
# Symbols for audit request parameters whose values are to be hidden when logging
|
69
|
+
AUDIT_FILTER_PARAMS = ["detail", "text"]
|
70
|
+
|
71
|
+
# Resource href for this agent
|
72
|
+
attr_reader :self_href
|
73
|
+
|
74
|
+
# Create RightApi client of specified type
|
75
|
+
#
|
76
|
+
# @param [AuthClient] auth_client providing authorization session for HTTP requests
|
77
|
+
#
|
78
|
+
# @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
|
79
|
+
# @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
|
80
|
+
# @option options [Numeric] :retry_timeout maximum before stop retrying; defaults to DEFAULT_RETRY_TIMEOUT
|
81
|
+
# @option options [Array] :retry_intervals between successive retries; defaults to DEFAULT_RETRY_INTERVALS
|
82
|
+
# @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
|
83
|
+
# @option options [Numeric] :reconnect_interval for reconnect attempts after lose connectivity
|
84
|
+
# @option options [Proc] :exception_callback for unexpected exceptions
|
85
|
+
#
|
86
|
+
# @raise [ArgumentError] auth client does not support this client type
|
87
|
+
def initialize(auth_client, options)
|
88
|
+
init(:api, auth_client, options.merge(:server_name => "RightApi", :api_version => API_VERSION))
|
89
|
+
end
|
90
|
+
|
91
|
+
# Route a request to a single target or multiple targets with no response expected
|
92
|
+
# Persist the request en route to reduce the chance of it being lost at the expense of some
|
93
|
+
# additional network overhead
|
94
|
+
# Enqueue the request if the target is not currently available
|
95
|
+
# Never automatically retry the request if there is the possibility of it being duplicated
|
96
|
+
# Set time-to-live to be forever
|
97
|
+
#
|
98
|
+
# @param [String] type of request as path specifying actor and action
|
99
|
+
# @param [Hash, NilClass] payload for request
|
100
|
+
# @param [String, Hash, NilClass] target for request, which may be identity of specific
|
101
|
+
# target, hash for selecting potentially multiple targets, or nil if routing solely
|
102
|
+
# using type; hash may contain:
|
103
|
+
# [Array] :tags that must all be associated with a target for it to be selected
|
104
|
+
# [Hash] :scope for restricting routing which may contain:
|
105
|
+
# [Integer] :account id that agents must be associated with to be included
|
106
|
+
# [Integer] :shard id that agents must be in to be included, or if value is
|
107
|
+
# Packet::GLOBAL, ones with no shard id
|
108
|
+
# [Symbol] :selector for picking from qualified targets: :any or :all;
|
109
|
+
# defaults to :any
|
110
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
111
|
+
# defaults to randomly generated ID
|
112
|
+
#
|
113
|
+
# @return [NilClass] always nil since there is no expected response to the request
|
114
|
+
#
|
115
|
+
# @raise [Exceptions::Unauthorized] authorization failed
|
116
|
+
# @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
|
117
|
+
# to it, or it is out of service or too busy to respond
|
118
|
+
# @raise [Exceptions::RetryableError] request failed but if retried may succeed
|
119
|
+
# @raise [Exceptions::Terminating] closing client and terminating service
|
120
|
+
# @raise [Exceptions::InternalServerError] internal error in server being accessed
|
121
|
+
def push(type, payload, target, token = nil)
|
122
|
+
map_request(type, payload, token)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Route a request to a single target with a response expected
|
126
|
+
# Automatically retry the request if a response is not received in a reasonable amount of time
|
127
|
+
# or if there is a non-delivery response indicating the target is not currently available
|
128
|
+
# Timeout the request if a response is not received in time, typically configured to 30 sec
|
129
|
+
# Because of retries there is the possibility of duplicated requests, and these are detected and
|
130
|
+
# discarded automatically for non-idempotent actions
|
131
|
+
# Allow the request to expire per the agent's configured time-to-live, typically 1 minute
|
132
|
+
#
|
133
|
+
# @param [String] type of request as path specifying actor and action
|
134
|
+
# @param [Hash, NilClass] payload for request
|
135
|
+
# @param [String, Hash, NilClass] target for request, which may be identity of specific
|
136
|
+
# target, hash for selecting targets of which one is picked randomly, or nil if routing solely
|
137
|
+
# using type; hash may contain:
|
138
|
+
# [Array] :tags that must all be associated with a target for it to be selected
|
139
|
+
# [Hash] :scope for restricting routing which may contain:
|
140
|
+
# [Integer] :account id that agents must be associated with to be included
|
141
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
142
|
+
# defaults to randomly generated ID
|
143
|
+
#
|
144
|
+
# @return [Result, NilClass] response from request
|
145
|
+
#
|
146
|
+
# @raise [Exceptions::Unauthorized] authorization failed
|
147
|
+
# @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
|
148
|
+
# to it, or it is out of service or too busy to respond
|
149
|
+
# @raise [Exceptions::RetryableError] request failed but if retried may succeed
|
150
|
+
# @raise [Exceptions::Terminating] closing client and terminating service
|
151
|
+
# @raise [Exceptions::InternalServerError] internal error in server being accessed
|
152
|
+
def request(type, payload, target, token = nil)
|
153
|
+
map_request(type, payload, token)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Determine whether request supported by this client
|
157
|
+
#
|
158
|
+
# @param [String] type of request as path specifying actor and action
|
159
|
+
#
|
160
|
+
# @return [Array] HTTP verb and path
|
161
|
+
def support?(type)
|
162
|
+
API_MAP.has_key?(type)
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
# Convert request to RightApi form and then make request via HTTP
|
168
|
+
#
|
169
|
+
# @param [String] type of request as path specifying actor and action
|
170
|
+
# @param [Hash, NilClass] payload for request
|
171
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
172
|
+
# defaults to randomly generated ID
|
173
|
+
#
|
174
|
+
# @return [Object, NilClass] response from request
|
175
|
+
#
|
176
|
+
# @raise [Exceptions::Unauthorized] authorization failed
|
177
|
+
# @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
|
178
|
+
# to it, or it is too busy to respond
|
179
|
+
# @raise [Exceptions::RetryableError] request failed but if retried may succeed
|
180
|
+
# @raise [Exceptions::Terminating] closing client and terminating service
|
181
|
+
# @raise [Exceptions::InternalServerError] internal error in server being accessed
|
182
|
+
def map_request(type, payload, token)
|
183
|
+
verb, path = API_MAP[type]
|
184
|
+
raise ArgumentError, "Unsupported request type: #{type}" if path.nil?
|
185
|
+
actor, action = type.split("/")[1..-1]
|
186
|
+
path, params, options = parameterize(actor, action, payload, path)
|
187
|
+
if action == "query_tags"
|
188
|
+
map_query_tags(verb, params, action, token, options)
|
189
|
+
else
|
190
|
+
map_response(make_request(verb, path, params, action, token, options), path)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Convert response from request into required form where necessary
|
195
|
+
#
|
196
|
+
# @param [Object] response received
|
197
|
+
# @param [String] path in URI for desired resource
|
198
|
+
#
|
199
|
+
# @return [Object] converted response
|
200
|
+
def map_response(response, path)
|
201
|
+
case path
|
202
|
+
when "/audit_entries"
|
203
|
+
# Convert returned audit entry href to audit ID
|
204
|
+
response.sub!(/^.*\/api\/audit_entries\//, "") if response.is_a?(String)
|
205
|
+
when "/tags/by_resource", "/tags/by_tag"
|
206
|
+
# Extract tags for each instance resource from response array with members of form
|
207
|
+
# {"actions" => [], "links" => [{"rel" => "resource", "href" => <href>}, ...]}, "tags" => [{"name" => <tag>}, ...]
|
208
|
+
tags = {}
|
209
|
+
if response
|
210
|
+
response.each do |hash|
|
211
|
+
r = {}
|
212
|
+
hash["links"].each { |l| r[l["href"]] = {"tags" => []} if l["href"] =~ /instances/ }
|
213
|
+
hash["tags"].each { |t| r.each_key { |k| r[k]["tags"] << t["name"] } } if r.any?
|
214
|
+
tags.merge!(r)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
response = tags
|
218
|
+
end
|
219
|
+
response
|
220
|
+
end
|
221
|
+
|
222
|
+
# Convert tag query request into one or more API requests and then convert responses
|
223
|
+
# Currently only retrieving "instances" resources
|
224
|
+
#
|
225
|
+
# @param [Symbol] verb for HTTP REST request
|
226
|
+
# @param [Hash] params for HTTP request
|
227
|
+
# @param [String] action from request type
|
228
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
229
|
+
# defaults to randomly generated ID
|
230
|
+
# @param [Hash] options augmenting or overriding default options for HTTP request
|
231
|
+
#
|
232
|
+
# @return [Hash] tags retrieved with resource href as key and tags array as value
|
233
|
+
def map_query_tags(verb, params, action, token, options)
|
234
|
+
response = {}
|
235
|
+
hrefs = params[:resource_hrefs] || []
|
236
|
+
hrefs.concat(query_by_tag(verb, params[:tags], action, token, options)) if params[:tags]
|
237
|
+
response = query_by_resource(verb, hrefs, action, token, options) if hrefs.any?
|
238
|
+
response
|
239
|
+
end
|
240
|
+
|
241
|
+
# Query API for resources with specified tags
|
242
|
+
#
|
243
|
+
# @param [Symbol] verb for HTTP REST request
|
244
|
+
# @param [Array] tags that all resources retrieved must have
|
245
|
+
# @param [String] action from request type
|
246
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
247
|
+
# defaults to randomly generated ID
|
248
|
+
# @param [Hash] options augmenting or overriding default options for HTTP request
|
249
|
+
#
|
250
|
+
# @return [Array] resource hrefs
|
251
|
+
def query_by_tag(verb, tags, action, token, options)
|
252
|
+
path = "/tags/by_tag"
|
253
|
+
params = {:tags => tags, :match_all => false, :resource_type => "instances"}
|
254
|
+
map_response(make_request(verb, path, params, action, token, options), path).keys
|
255
|
+
end
|
256
|
+
|
257
|
+
# Query API for tags associated with a set of resources
|
258
|
+
#
|
259
|
+
# @param [Symbol] verb for HTTP REST request
|
260
|
+
# @param [Array] hrefs for resources whose tags are to be retrieved
|
261
|
+
# @param [String] action from request type
|
262
|
+
# @param [String, NilClass] token uniquely identifying this request;
|
263
|
+
# defaults to randomly generated ID
|
264
|
+
# @param [Hash] options augmenting or overriding default options for HTTP request
|
265
|
+
#
|
266
|
+
# @return [Hash] tags retrieved with resource href as key and tags array as value
|
267
|
+
def query_by_resource(verb, hrefs, action, token, options)
|
268
|
+
path = "/tags/by_resource"
|
269
|
+
params = {:resource_hrefs => hrefs}
|
270
|
+
map_response(make_request(verb, path, params, action, token, options), path)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Convert payload to HTTP parameters
|
274
|
+
#
|
275
|
+
# @param [String] actor from request type
|
276
|
+
# @param [String] action from request type
|
277
|
+
# @param [Hash, NilClass] payload for request
|
278
|
+
# @param [String] path in URI for desired resource
|
279
|
+
#
|
280
|
+
# @return [Array] path string and parameters and options hashes
|
281
|
+
def parameterize(actor, action, payload, path)
|
282
|
+
options = {}
|
283
|
+
params = {}
|
284
|
+
if actor == "auditor"
|
285
|
+
path = path.sub(/:id/, payload[:audit_id].to_s || "")
|
286
|
+
params = parameterize_audit(action, payload)
|
287
|
+
options = {:filter_params => AUDIT_FILTER_PARAMS}
|
288
|
+
elsif actor == "router" && action =~ /_tags/
|
289
|
+
if action != "query_tags"
|
290
|
+
params[:resource_hrefs] = [@self_href]
|
291
|
+
else
|
292
|
+
params[:resource_hrefs] = Array(payload[:hrefs]).flatten.compact if payload[:hrefs]
|
293
|
+
end
|
294
|
+
params[:tags] = Array(payload[:tags]).flatten.compact if payload[:tags]
|
295
|
+
else
|
296
|
+
# Can remove :agent_identity here since now carried in the authorization as the :agent
|
297
|
+
payload.each { |k, v| params[k.to_sym] = v if k.to_sym != :agent_identity } if payload.is_a?(Hash)
|
298
|
+
end
|
299
|
+
[path, params, options]
|
300
|
+
end
|
301
|
+
|
302
|
+
# Translate audit request payload to HTTP parameters
|
303
|
+
# Truncate audit summary to MAX_AUDIT_SUMMARY_LENGTH, the limit imposed by RightApi
|
304
|
+
#
|
305
|
+
# @param [String] action requested: create_entry or update_entry
|
306
|
+
# @param [Hash] payload from submitted request
|
307
|
+
#
|
308
|
+
# @return [Hash] HTTP request parameters
|
309
|
+
#
|
310
|
+
# @raise [ArgumentError] unknown request action
|
311
|
+
def parameterize_audit(action, payload)
|
312
|
+
params = {}
|
313
|
+
summary = non_blank(payload[:summary])
|
314
|
+
detail = non_blank(payload[:detail])
|
315
|
+
case action
|
316
|
+
when "create_entry"
|
317
|
+
params[:audit_entry] = {:auditee_href => @self_href}
|
318
|
+
params[:audit_entry][:summary] = truncate(summary, MAX_AUDIT_SUMMARY_LENGTH) if summary
|
319
|
+
params[:audit_entry][:detail] = detail if detail
|
320
|
+
if (user_email = non_blank(payload[:user_email]))
|
321
|
+
params[:user_email] = user_email
|
322
|
+
end
|
323
|
+
params[:notify] = payload[:category] if payload[:category]
|
324
|
+
when "update_entry"
|
325
|
+
params[:offset] = payload[:offset] if payload[:offset]
|
326
|
+
if summary
|
327
|
+
params[:summary] = truncate(summary, MAX_AUDIT_SUMMARY_LENGTH)
|
328
|
+
params[:notify] = payload[:category] if payload[:category]
|
329
|
+
end
|
330
|
+
params[:detail] = detail if detail
|
331
|
+
else
|
332
|
+
raise ArgumentError, "Unknown audit request action: #{action}"
|
333
|
+
end
|
334
|
+
params
|
335
|
+
end
|
336
|
+
|
337
|
+
# Truncate string if it exceeds maximum length
|
338
|
+
# Do length check with bytesize rather than size since this code
|
339
|
+
# is running with ruby 1.9.2 while the API uses 1.8.7, otherwise
|
340
|
+
# multi-byte characters could cause this code to be too lenient
|
341
|
+
#
|
342
|
+
# @param [String, NilClass] value to be truncated
|
343
|
+
# @param [Integer] max_length allowed; must be greater than 3
|
344
|
+
#
|
345
|
+
# @return [String, NilClass] truncated string or original value if it is not a string
|
346
|
+
#
|
347
|
+
# @raise [ArgumentError] max_length too small
|
348
|
+
def truncate(value, max_length)
|
349
|
+
raise ArgumentError, "max_length must be greater than 3" if max_length <= 3
|
350
|
+
if value.is_a?(String) && value.bytesize > max_length
|
351
|
+
max_truncated = max_length - 3
|
352
|
+
truncated = value[0, max_truncated]
|
353
|
+
while truncated.bytesize > max_truncated do
|
354
|
+
truncated.chop!
|
355
|
+
end
|
356
|
+
truncated + "..."
|
357
|
+
else
|
358
|
+
value
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Determine whether value is non-blank
|
363
|
+
#
|
364
|
+
# @param [String, NilClass] value to be tested
|
365
|
+
#
|
366
|
+
# @return [String, NilClass] value if non-blank, otherwise nil
|
367
|
+
def non_blank(value)
|
368
|
+
value && !value.empty? ? value : nil
|
369
|
+
end
|
370
|
+
|
371
|
+
# Perform any other steps needed to make this client fully usable
|
372
|
+
# once HTTP client has been created and server known to be accessible
|
373
|
+
#
|
374
|
+
# @return [TrueClass] always true
|
375
|
+
def enable_use
|
376
|
+
result = make_request(:get, "/sessions/instance", {}, "instance")
|
377
|
+
@self_href = result["links"].select { |link| link["rel"] == "self" }.first["href"]
|
378
|
+
true
|
379
|
+
end
|
380
|
+
|
381
|
+
end # ApiClient
|
382
|
+
|
383
|
+
end # RightScale
|
@@ -0,0 +1,247 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
|
26
|
+
# Abstract base class for authorization client
|
27
|
+
class AuthClient
|
28
|
+
|
29
|
+
# State of authorization: :pending, :authorized, :unauthorized, :expired, :failed, :closed
|
30
|
+
attr_reader :state
|
31
|
+
|
32
|
+
PERMITTED_STATE_TRANSITIONS = {
|
33
|
+
:pending => [:pending, :authorized, :unauthorized, :failed, :closed],
|
34
|
+
:authorized => [:authorized, :unauthorized, :expired, :failed, :closed],
|
35
|
+
:unauthorized => [:authorized, :unauthorized, :failed, :closed],
|
36
|
+
:expired => [:authorized, :unauthorized, :expired, :failed, :closed],
|
37
|
+
:failed => [:failed, :closed],
|
38
|
+
:closed => [:closed] }
|
39
|
+
|
40
|
+
# Initialize client
|
41
|
+
# Derived classes need to call reset_stats
|
42
|
+
def initialize(options = {})
|
43
|
+
raise NotImplementedError, "#{self.class.name} is an abstract class"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Identity of agent using this client
|
47
|
+
#
|
48
|
+
# @return [String] identity
|
49
|
+
def identity
|
50
|
+
@identity
|
51
|
+
end
|
52
|
+
|
53
|
+
# Headers to be added to HTTP request
|
54
|
+
# Include authorization header by default
|
55
|
+
#
|
56
|
+
# @return [Hash] headers to be added to request header
|
57
|
+
#
|
58
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
59
|
+
# @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
|
60
|
+
def headers
|
61
|
+
check_authorized
|
62
|
+
auth_header
|
63
|
+
end
|
64
|
+
|
65
|
+
# Authorization header to be added to HTTP request
|
66
|
+
#
|
67
|
+
# @return [Hash] authorization header
|
68
|
+
#
|
69
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
70
|
+
# @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
|
71
|
+
def auth_header
|
72
|
+
check_authorized
|
73
|
+
{"Authorization" => "Bearer #{@access_token}"}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Account if any to which agent using this client belongs
|
77
|
+
#
|
78
|
+
# @return [Integer] account ID
|
79
|
+
#
|
80
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
81
|
+
def account_id
|
82
|
+
check_authorized
|
83
|
+
@account_id
|
84
|
+
end
|
85
|
+
|
86
|
+
# URL for accessing RightApi including base path
|
87
|
+
#
|
88
|
+
# @return [String] URL including base path
|
89
|
+
#
|
90
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
91
|
+
# @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
|
92
|
+
def api_url
|
93
|
+
check_authorized
|
94
|
+
@api_url
|
95
|
+
end
|
96
|
+
|
97
|
+
# URL for accessing RightNet router including base path
|
98
|
+
#
|
99
|
+
# @return [String] URL including base path
|
100
|
+
#
|
101
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
102
|
+
# @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
|
103
|
+
def router_url
|
104
|
+
check_authorized
|
105
|
+
@router_url
|
106
|
+
end
|
107
|
+
|
108
|
+
# RightNet communication mode
|
109
|
+
#
|
110
|
+
# @return [Symbol] :http or :amqp
|
111
|
+
def mode
|
112
|
+
@mode
|
113
|
+
end
|
114
|
+
|
115
|
+
# An HTTP request had an authorization expiration error
|
116
|
+
# Renew authorization
|
117
|
+
#
|
118
|
+
# @return [TrueClass] always true
|
119
|
+
def expired
|
120
|
+
Log.info("Renewing authorization for #{identity} because request failed due to expiration")
|
121
|
+
self.state = :expired
|
122
|
+
renew_authorization
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
# An HTTP request received a redirect response
|
127
|
+
#
|
128
|
+
# @param [String] location to which response indicated to redirect
|
129
|
+
#
|
130
|
+
# @return [TrueClass] always true
|
131
|
+
def redirect(location)
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Take any actions necessary to quiesce client interaction in preparation
|
136
|
+
# for agent termination but allow any active requests to complete
|
137
|
+
#
|
138
|
+
# @return [TrueClass] always true
|
139
|
+
def close
|
140
|
+
self.state = :closed
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
# Record callback to be notified of authorization status changes
|
145
|
+
# Multiple callbacks are supported
|
146
|
+
#
|
147
|
+
# @yield [type, status] called when status changes (optional)
|
148
|
+
# @yieldparam [Symbol] type of client reporting status change: :auth
|
149
|
+
# @yieldparam [Symbol] state of authorization
|
150
|
+
#
|
151
|
+
# @return [Symbol] current state
|
152
|
+
def status(&callback)
|
153
|
+
@status_callbacks = (@status_callbacks || []) << callback if callback
|
154
|
+
state
|
155
|
+
end
|
156
|
+
|
157
|
+
# Current statistics for this client
|
158
|
+
#
|
159
|
+
# @param [Boolean] reset the statistics after getting the current ones
|
160
|
+
#
|
161
|
+
# @return [Hash] current statistics
|
162
|
+
# [Hash, NilClass] "state" Activity stats or nil if none
|
163
|
+
# [Hash, NilClass] "exceptions" Exceptions stats or nil if none
|
164
|
+
def stats(reset = false)
|
165
|
+
stats = {}
|
166
|
+
@stats.each { |k, v| stats[k] = v.all }
|
167
|
+
reset_stats if reset
|
168
|
+
stats
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
# Reset statistics for this client
|
174
|
+
#
|
175
|
+
# @return [TrueClass] always true
|
176
|
+
def reset_stats
|
177
|
+
@stats = {
|
178
|
+
"state" => RightSupport::Stats::Activity.new,
|
179
|
+
"exceptions" => RightSupport::Stats::Exceptions.new(agent = nil, @exception_callback)}
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
# Check whether authorized
|
184
|
+
#
|
185
|
+
# @return [TrueClass] always true if don't raise exception
|
186
|
+
#
|
187
|
+
# @raise [Exceptions::Unauthorized] not authorized
|
188
|
+
# @raise [Exceptions::RetryableError] authorization expired, but retry may succeed
|
189
|
+
def check_authorized
|
190
|
+
if state == :expired
|
191
|
+
raise Exceptions::RetryableError, "Authorization expired"
|
192
|
+
elsif state != :authorized
|
193
|
+
raise Exceptions::Unauthorized, "Not authorized with RightScale" if state != :authorized
|
194
|
+
end
|
195
|
+
true
|
196
|
+
end
|
197
|
+
|
198
|
+
# Renew authorization
|
199
|
+
#
|
200
|
+
# @param [Integer] wait time before attempt to renew
|
201
|
+
#
|
202
|
+
# @return [TrueClass] always true
|
203
|
+
def renew_authorization(wait = 0)
|
204
|
+
true
|
205
|
+
end
|
206
|
+
|
207
|
+
# Update authorization state
|
208
|
+
# If state has changed, make external callbacks to notify of change
|
209
|
+
# Do not update state once set to :closed
|
210
|
+
#
|
211
|
+
# @param [Hash] value for new state
|
212
|
+
#
|
213
|
+
# @return [Symbol] updated state
|
214
|
+
#
|
215
|
+
# @raise [ArgumentError] invalid state transition
|
216
|
+
def state=(value)
|
217
|
+
return if @state == :closed
|
218
|
+
unless PERMITTED_STATE_TRANSITIONS[@state].include?(value)
|
219
|
+
raise ArgumentError, "Invalid state transition: #{@state.inspect} -> #{value.inspect}"
|
220
|
+
end
|
221
|
+
|
222
|
+
case value
|
223
|
+
when :pending, :closed
|
224
|
+
@stats["state"].update(value.to_s)
|
225
|
+
@state = value
|
226
|
+
when :authorized, :unauthorized, :expired, :failed
|
227
|
+
if value != @state
|
228
|
+
@stats["state"].update(value.to_s)
|
229
|
+
@state = value
|
230
|
+
(@status_callbacks || []).each do |callback|
|
231
|
+
begin
|
232
|
+
callback.call(:auth, @state)
|
233
|
+
rescue StandardError => e
|
234
|
+
Log.error("Failed status callback", e)
|
235
|
+
@stats["exceptions"].track("status", e)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
raise ArgumentError, "Unknown state: #{value.inspect}"
|
241
|
+
end
|
242
|
+
@state
|
243
|
+
end
|
244
|
+
|
245
|
+
end # AuthClient
|
246
|
+
|
247
|
+
end # RightScale
|