right_link 5.9.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.
Files changed (199) hide show
  1. data/actors/agent_manager.rb +88 -0
  2. data/actors/instance_scheduler.rb +321 -0
  3. data/actors/instance_services.rb +64 -0
  4. data/actors/instance_setup.rb +567 -0
  5. data/bin/cloud +25 -0
  6. data/bin/cook_runner +44 -0
  7. data/bin/deploy +120 -0
  8. data/bin/enroll +385 -0
  9. data/bin/rad +32 -0
  10. data/bin/rchk +29 -0
  11. data/bin/rnac +39 -0
  12. data/bin/rs_connect +33 -0
  13. data/bin/rs_log_level +31 -0
  14. data/bin/rs_ohai +28 -0
  15. data/bin/rs_reenroll +31 -0
  16. data/bin/rs_run_recipe +34 -0
  17. data/bin/rs_run_right_script +34 -0
  18. data/bin/rs_shutdown +33 -0
  19. data/bin/rs_tag +33 -0
  20. data/bin/rs_thunk +33 -0
  21. data/bin/rstat +31 -0
  22. data/bin/system +16 -0
  23. data/ext/Rakefile +18 -0
  24. data/init/config.yml +5 -0
  25. data/init/init.rb +79 -0
  26. data/lib/chef/ohai_setup.rb +51 -0
  27. data/lib/chef/plugins/cloud.rb +91 -0
  28. data/lib/chef/plugins/cloudstack.rb +23 -0
  29. data/lib/chef/plugins/ec2.rb +23 -0
  30. data/lib/chef/plugins/linux/block_device2.rb +24 -0
  31. data/lib/chef/plugins/rackspace.rb +23 -0
  32. data/lib/chef/plugins/rightscale.rb +125 -0
  33. data/lib/chef/plugins/windows/network.rb +114 -0
  34. data/lib/chef/plugins.rb +74 -0
  35. data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
  36. data/lib/chef/providers/dns_resource.rb +100 -0
  37. data/lib/chef/providers/executable_schedule_provider.rb +70 -0
  38. data/lib/chef/providers/executable_schedule_resource.rb +144 -0
  39. data/lib/chef/providers/remote_recipe_provider.rb +86 -0
  40. data/lib/chef/providers/remote_recipe_resource.rb +101 -0
  41. data/lib/chef/providers/right_link_tag_provider.rb +73 -0
  42. data/lib/chef/providers/right_link_tag_resource.rb +59 -0
  43. data/lib/chef/providers/right_script_provider.rb +190 -0
  44. data/lib/chef/providers/right_script_resource.rb +113 -0
  45. data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
  46. data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
  47. data/lib/chef/providers/server_collection_provider.rb +66 -0
  48. data/lib/chef/providers/server_collection_resource.rb +93 -0
  49. data/lib/chef/providers/windows/powershell_provider.rb +151 -0
  50. data/lib/chef/providers/windows/powershell_resource.rb +111 -0
  51. data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
  52. data/lib/chef/right_providers.rb +55 -0
  53. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
  54. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
  55. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
  56. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
  57. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
  58. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
  59. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
  60. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
  61. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
  62. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
  63. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
  64. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
  65. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
  66. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
  67. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
  68. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
  69. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
  70. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
  71. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
  72. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
  73. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
  74. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  75. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
  76. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
  77. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
  78. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
  79. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
  80. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
  81. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
  82. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
  83. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
  84. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
  85. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
  86. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
  87. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
  88. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
  89. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
  90. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
  91. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
  92. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
  93. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  94. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
  95. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
  96. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
  97. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
  98. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
  99. data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
  100. data/lib/chef/windows/chef_node_server.rb +463 -0
  101. data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
  102. data/lib/chef/windows/pipe_server.rb +283 -0
  103. data/lib/chef/windows/powershell_host.rb +285 -0
  104. data/lib/chef/windows/powershell_pipe_server.rb +136 -0
  105. data/lib/chef/windows/powershell_provider_base.rb +92 -0
  106. data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
  107. data/lib/clouds/cloud.rb +557 -0
  108. data/lib/clouds/cloud_factory.rb +250 -0
  109. data/lib/clouds/cloud_utilities.rb +244 -0
  110. data/lib/clouds/clouds/azure.rb +106 -0
  111. data/lib/clouds/clouds/cloudstack.rb +114 -0
  112. data/lib/clouds/clouds/ec2.rb +113 -0
  113. data/lib/clouds/clouds/eucalyptus.rb +46 -0
  114. data/lib/clouds/clouds/google.rb +102 -0
  115. data/lib/clouds/clouds/none.rb +76 -0
  116. data/lib/clouds/clouds/openstack.rb +30 -0
  117. data/lib/clouds/clouds/rackspace-ng.rb +54 -0
  118. data/lib/clouds/clouds/rackspace.rb +78 -0
  119. data/lib/clouds/clouds/softlayer.rb +91 -0
  120. data/lib/clouds/metadata_formatter.rb +108 -0
  121. data/lib/clouds/metadata_provider.rb +128 -0
  122. data/lib/clouds/metadata_source.rb +87 -0
  123. data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
  124. data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
  125. data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
  126. data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
  127. data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
  128. data/lib/clouds/metadata_tree_climber.rb +144 -0
  129. data/lib/clouds/metadata_writer.rb +155 -0
  130. data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
  131. data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
  132. data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
  133. data/lib/clouds/register_clouds.rb +34 -0
  134. data/lib/clouds.rb +32 -0
  135. data/lib/gem_dependencies.rb +83 -0
  136. data/lib/git_hooks/commit-msg.rb +7 -0
  137. data/lib/instance/agent_config.rb +168 -0
  138. data/lib/instance/agent_watcher.rb +233 -0
  139. data/lib/instance/audit_cook_stub.rb +104 -0
  140. data/lib/instance/audit_proxy.rb +247 -0
  141. data/lib/instance/bundle_queue.rb +104 -0
  142. data/lib/instance/cook/agent_connection.rb +109 -0
  143. data/lib/instance/cook/audit_logger.rb +165 -0
  144. data/lib/instance/cook/audit_stub.rb +142 -0
  145. data/lib/instance/cook/ca-bundle.crt +2794 -0
  146. data/lib/instance/cook/chef_state.rb +211 -0
  147. data/lib/instance/cook/cook.rb +306 -0
  148. data/lib/instance/cook/cook_state.rb +298 -0
  149. data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
  150. data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
  151. data/lib/instance/cook/executable_sequence.rb +765 -0
  152. data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
  153. data/lib/instance/cook/repose_downloader.rb +349 -0
  154. data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
  155. data/lib/instance/cook.rb +41 -0
  156. data/lib/instance/downloader.rb +208 -0
  157. data/lib/instance/duplicable.rb +67 -0
  158. data/lib/instance/exceptions.rb +49 -0
  159. data/lib/instance/executable_sequence_proxy.rb +278 -0
  160. data/lib/instance/instance_commands.rb +577 -0
  161. data/lib/instance/instance_state.rb +633 -0
  162. data/lib/instance/json_utilities.rb +102 -0
  163. data/lib/instance/login_manager.rb +533 -0
  164. data/lib/instance/login_user_manager.rb +522 -0
  165. data/lib/instance/message_encoder.rb +118 -0
  166. data/lib/instance/multi_thread_bundle_queue.rb +232 -0
  167. data/lib/instance/operation_context.rb +60 -0
  168. data/lib/instance/options_bag.rb +65 -0
  169. data/lib/instance/payload_formatter.rb +46 -0
  170. data/lib/instance/policy.rb +53 -0
  171. data/lib/instance/policy_audit.rb +100 -0
  172. data/lib/instance/policy_manager.rb +146 -0
  173. data/lib/instance/reenroll_manager.rb +104 -0
  174. data/lib/instance/right_scripts_cookbook.rb +181 -0
  175. data/lib/instance/shutdown_request.rb +221 -0
  176. data/lib/instance/single_thread_bundle_queue.rb +189 -0
  177. data/lib/instance/volume_management.rb +450 -0
  178. data/lib/instance.rb +50 -0
  179. data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
  180. data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
  181. data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
  182. data/lib/repo_conf_generators/rightscale_key.pub +17 -0
  183. data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
  184. data/lib/repo_conf_generators.rb +30 -0
  185. data/lib/run_shell.rb +28 -0
  186. data/scripts/agent_checker.rb +571 -0
  187. data/scripts/agent_controller.rb +247 -0
  188. data/scripts/agent_deployer.rb +148 -0
  189. data/scripts/bundle_runner.rb +336 -0
  190. data/scripts/cloud_controller.rb +176 -0
  191. data/scripts/log_level_manager.rb +142 -0
  192. data/scripts/ohai_runner.rb +33 -0
  193. data/scripts/reenroller.rb +193 -0
  194. data/scripts/server_importer.rb +293 -0
  195. data/scripts/shutdown_client.rb +183 -0
  196. data/scripts/system_configurator.rb +367 -0
  197. data/scripts/tagger.rb +381 -0
  198. data/scripts/thunker.rb +356 -0
  199. metadata +418 -0
@@ -0,0 +1,250 @@
1
+ #
2
+ # Copyright (c) 2011 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
+ # Singleton for registering and instantiating clouds.
27
+ class CloudFactory
28
+
29
+ include RightSupport::Ruby::EasySingleton
30
+
31
+ # the unknown cloud is used to automatically detect current instance's cloud
32
+ UNKNOWN_CLOUD_NAME = :unknown
33
+
34
+ # exceptions
35
+ class UnknownCloud < Exception; end
36
+
37
+ # Registry method for a dynamic metadata type.
38
+ #
39
+ # === Parameters
40
+ # cloud_names(Array|String):: name of one or more clouds (which may include DEFAULT_CLOUD) that use the given type
41
+ # cloud_script_path(String):: path to script used to describe cloud on creation
42
+ #
43
+ # === Return
44
+ # always true
45
+ def register(cloud_names, cloud_script_path)
46
+ # relies on each to split on newlines for strings and otherwise do each for collections.
47
+ cloud_script_path = File.normalize_path(cloud_script_path)
48
+ cloud_names.each { |cloud_name| registered_type(cloud_name, cloud_script_path) }
49
+ true
50
+ end
51
+
52
+ # Resets the global cloud registry (to ensure a clean reload of cloud names).
53
+ #
54
+ # === Return
55
+ # result(Hash):: Hash of cloud names to script paths before reset
56
+ def reset_registry
57
+ result = @names_to_script_paths
58
+ @names_to_script_paths = nil
59
+ result
60
+ end
61
+
62
+ # Gets the path to the script describing a cloud.
63
+ #
64
+ # === Parameters
65
+ # cloud_name(String):: a registered_type cloud name
66
+ #
67
+ # === Return
68
+ # cloud_script_path(String):: path to script used to describe cloud on creation
69
+ #
70
+ # === Raise
71
+ # UnknownCloud:: on error
72
+ def registered_script_path(cloud_name)
73
+ cloud_script_path = registered_type(cloud_name)
74
+ raise UnknownCloud.new("Unknown cloud: #{cloud_name}") unless cloud_script_path
75
+ return cloud_script_path
76
+ end
77
+
78
+ # Factory method for dynamic metadata types.
79
+ #
80
+ # === Parameters
81
+ # cloud(String):: a registered_type cloud name
82
+ # options(Hash):: options for creation
83
+ #
84
+ # === Return
85
+ # result(Object):: new instance of registered_type metadata type
86
+ #
87
+ # === Raise
88
+ # UnknownCloud:: on error
89
+ def create(cloud_name, options)
90
+ raise ArgumentError.new("cloud_name is required") if cloud_name.to_s.empty?
91
+ raise ArgumentError.new("options[:logger] is required") unless logger = options[:logger]
92
+ raise UnknownCloud.new("No cloud definitions available.") unless @names_to_script_paths
93
+ cloud_name = cloud_name.to_sym
94
+ cloud_name = default_cloud_name if UNKNOWN_CLOUD_NAME == cloud_name
95
+ if UNKNOWN_CLOUD_NAME == cloud_name
96
+ # persist default cloud name after successful detection.
97
+ cloud = detect_cloud(options)
98
+ raise UnknownCloud.new("Unable to determine a default cloud") unless cloud
99
+ default_cloud_name(cloud.name)
100
+ return cloud
101
+ end
102
+ cloud_script_path = registered_script_path(cloud_name)
103
+ options = options.dup
104
+ options[:name] ||= cloud_name.to_s
105
+ options[:script_path] = cloud_script_path
106
+ cloud = Cloud.new(options)
107
+ text = File.read(cloud_script_path)
108
+ cloud.instance_eval(text)
109
+ cloud.abbreviation(cloud_name) unless cloud.abbreviation
110
+ extend_cloud_by_scripts(cloud, logger)
111
+
112
+ # finalize defaults only after all cloud definitions have been evaluated
113
+ # by the new cloud object.
114
+ cloud.finalize_default_options
115
+ return cloud
116
+ end
117
+
118
+ # Setter/getter for the default cloud name. This currently relies on a
119
+ # 'cloud file' which must be present in an expected RightScale location.
120
+ #
121
+ # === Parameters
122
+ # value(String|Token):: default cloud name or nil
123
+ #
124
+ # === Return
125
+ # result(String):: default cloud name or nil
126
+ def default_cloud_name(value = nil)
127
+ cloud_file_path = RightScale::AgentConfig.cloud_file_path
128
+ if value
129
+ parent_dir = File.dirname(cloud_file_path)
130
+ FileUtils.mkdir_p(parent_dir) unless File.directory?(parent_dir)
131
+ File.open(cloud_file_path, "w") { |f| f.write(value.to_s) }
132
+ else
133
+ value = File.read(cloud_file_path).strip if File.file?(cloud_file_path)
134
+ end
135
+ value.to_s.empty? ? UNKNOWN_CLOUD_NAME : value
136
+ end
137
+
138
+ # Attempts to detect the current instance's cloud by instantiating the
139
+ # various known clouds and running their detection methods.
140
+ #
141
+ # === Parameters
142
+ # options(Hash):: options for creation or empty
143
+ #
144
+ # === Return
145
+ # cloud(Cloud):: detected cloud or nil
146
+ def detect_cloud(options)
147
+ @names_to_script_paths.each_key do |cloud_name|
148
+ begin
149
+ cloud = create(cloud_name, options)
150
+ return cloud if cloud.is_current_cloud?
151
+ rescue Exception
152
+ # ignore failures and proceed to detecting next cloud, if any.
153
+ end
154
+ end
155
+ nil
156
+ end
157
+
158
+ # Normalizes a cloud name to ensure all variants are resolvable.
159
+ #
160
+ # === Parameters
161
+ # cloud_name(String):: cloud name
162
+ #
163
+ # === Return
164
+ # result(String):: normalized cloud name
165
+ def self.normalize_cloud_name(cloud_name)
166
+ return cloud_name.to_s.strip.downcase
167
+ end
168
+
169
+ protected
170
+
171
+ # Getter/setter for cloud types registered clouds.
172
+ #
173
+ # === Parameters
174
+ # name(String):: name of cloud
175
+ # script_path(String):: path to script to evaluate when creating cloud
176
+ #
177
+ # === Return
178
+ # result(Hash):: hash in form {:name => <name>, :script_path => <script_path>} or nil
179
+ def registered_type(cloud_name, cloud_script_path = nil)
180
+ raise ArgumentError.new("cloud_name is required") unless cloud_name
181
+ key = self.class.normalize_cloud_name(cloud_name).to_sym
182
+ @names_to_script_paths ||= {}
183
+ @names_to_script_paths[key] ||= cloud_script_path
184
+ end
185
+
186
+ # Supports runtime extension of the cloud object by external scripts which
187
+ # are associated with instance methods. These scripts can also override the
188
+ # predefined methods (e.g. write_metadata) to further customize a cloud's
189
+ # behavior on a given instance. It may also be better to run some complex
190
+ # operation in a child process instead of in the process which is loading
191
+ # the cloud object.
192
+ #
193
+ # === Parameters
194
+ # @param [String] cloud as a registered_type or UNKNOWN_CLOUD_NAME
195
+ # @param [Logger] logger
196
+ def extend_cloud_by_scripts(cloud, logger)
197
+ # search for script directories based first on any clouds which were
198
+ # extended by the cloud and then by the exact cloud name.
199
+ cloud_name = cloud.name.to_s
200
+ cloud_aliases = cloud.extended_clouds + [cloud_name]
201
+
202
+ search_paths = []
203
+ cloud_aliases.each do |cloud_alias|
204
+ # first add default search path for cloud name.
205
+ search_path = File.join(RightScale::Platform.filesystem.private_bin_dir, cloud_alias)
206
+ if File.directory?(search_path)
207
+ search_paths << search_path
208
+ logger.debug("Added #{search_path.inspect} to search path for extension scripts.")
209
+ else
210
+ logger.debug("Optional extension script dir #{search_path.inspect} does not exist.")
211
+ end
212
+
213
+ # custom paths are last in order to supercede any preceeding extensions.
214
+ cloud.extension_script_base_paths.each do |base_path|
215
+ search_path = File.join(base_path, cloud_alias)
216
+ if File.directory?(search_path)
217
+ search_paths << search_path
218
+ logger.debug("Added #{search_path.inspect} to search path for extension scripts.")
219
+ else
220
+ logger.debug("Optional extension script dir #{search_path.inspect} does not exist.")
221
+ end
222
+ end
223
+ end
224
+
225
+ # inject any scripts discovered in script paths as instance methods which
226
+ # return the result of calling the external script.
227
+ search_paths.each do |search_path|
228
+ search_path = File.normalize_path(search_path)
229
+ Dir.glob(File.join(search_path, "*")).each do |script_path|
230
+ script_ext = File.extname(script_path)
231
+ script_name = File.basename(script_path, script_ext)
232
+
233
+ # ignore any script names which contain strange characters (like
234
+ # semicolon) for security reasons.
235
+ if script_name =~ /^[_A-Za-z][_A-Za-z0-9]*$/
236
+ logger.debug("Extending #{cloud_name} from #{script_path.inspect}")
237
+ eval_me = <<EOF
238
+ def #{script_name}(*arguments)
239
+ return execute_script(\"#{script_path}\", *arguments)
240
+ end
241
+ EOF
242
+ cloud.instance_eval(eval_me)
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ end # CloudFactory
249
+
250
+ end # RightScale
@@ -0,0 +1,244 @@
1
+ #
2
+ # Copyright (c) 2011 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
+ require 'open-uri'
24
+ require 'socket'
25
+
26
+ module RightScale
27
+
28
+ class CloudUtilities
29
+
30
+ IP_ADDRESS_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
31
+
32
+ DEFAULT_WHATS_MY_IP_HOST_NAME = 'eip-us-east.rightscale.com'
33
+ DEFAULT_WHATS_MY_IP_TIMEOUT = 10 * 60
34
+ DEFAULT_WHATS_MY_IP_RETRY_DELAY = 5
35
+
36
+ # Does an interface have the given mac address?
37
+ #
38
+ # === Parameters
39
+ # ohai(Mash):: ohai state
40
+ # mac(String):: MAC address to find
41
+ #
42
+ # === Return
43
+ # (Boolean):: true if there is an interface with the given mac address
44
+ def self.has_mac?(ohai, mac)
45
+ !!ohai[:network][:interfaces].values.detect { |iface| !iface[:arp].nil? && iface[:arp].value?(mac) }
46
+ end
47
+
48
+ # Attempt to connect to the given address on the given port as a quick verification
49
+ # that the metadata service is available.
50
+ #
51
+ # === Parameters
52
+ # addr(String):: address of the metadata service
53
+ # port(Number):: port of the metadata service
54
+ # timeout(Number)::Optional - time to wait for a response
55
+ #
56
+ # === Return
57
+ # connected(Boolean):: true if a connection could be made, false otherwise
58
+ def self.can_contact_metadata_server?(addr, port, timeout=2)
59
+ t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
60
+ saddr = Socket.pack_sockaddr_in(port, addr)
61
+ connected = false
62
+
63
+ begin
64
+ t.connect_nonblock(saddr)
65
+ rescue Errno::EINPROGRESS
66
+ r, w, e = IO::select(nil, [t], nil, timeout)
67
+ if !w.nil?
68
+ connected = true
69
+ else
70
+ begin
71
+ t.connect_nonblock(saddr)
72
+ rescue Errno::EISCONN
73
+ t.close
74
+ connected = true
75
+ rescue SystemCallError
76
+ end
77
+ end
78
+ rescue SystemCallError
79
+ end
80
+
81
+ connected
82
+ end
83
+
84
+ # Finds the first ip address for a given interface in the ohai mash
85
+ #
86
+ # === Parameters
87
+ # ohai(Mash):: ohai state
88
+ # interface(Symbol):: symbol of the interface (:eth0, :eth1, ...)
89
+ #
90
+ # === Return
91
+ # address(String|nil):: ip address associated with the given interface, nil if interface could not be found
92
+ def self.ip_for_interface(ohai, interface)
93
+ address = nil
94
+ if ohai[:network] != nil &&
95
+ ohai[:network][:interfaces] != nil &&
96
+ ohai[:network][:interfaces][interface] != nil &&
97
+ ohai[:network][:interfaces][interface][:addresses] != nil
98
+
99
+ addresses = ohai[:network][:interfaces][interface][:addresses].find { |key, item| item['family'] == 'inet' }
100
+ address = addresses.first unless addresses.nil?
101
+ end
102
+
103
+ address
104
+ end
105
+
106
+ # Finds the first ip address that matches a given connection type (private|public)
107
+ #
108
+ # === Parameters
109
+ # ohai(Mash):: ohai state
110
+ # connection_type(Symbol):: either 'public' or 'private'
111
+ #
112
+ # === Return
113
+ # address(String|nil):: ip address associated with the given interface, nil if interface could not be found
114
+ def self.ip_for_windows_interface(ohai, connection_type)
115
+ address = nil
116
+
117
+ # find the right interface
118
+ if ohai[:network] != nil &&
119
+ ohai[:network][:interfaces] != nil
120
+ interface = ohai[:network][:interfaces].values.find do |item|
121
+ !(item.nil? || item[:instance].nil?) && item[:instance][:net_connection_id] == connection_type
122
+ end
123
+
124
+ # grab the ip if there is one
125
+ if interface != nil &&
126
+ interface["configuration"] != nil &&
127
+ interface["configuration"]["ip_address"] != nil
128
+ address = interface["configuration"]["ip_address"].first
129
+ end
130
+ end
131
+
132
+ address
133
+ end
134
+
135
+ # Splits data on the given splitter character and merges to the given hash.
136
+ #
137
+ # === Parameters
138
+ # data(String):: raw data
139
+ # splitter(String):: splitter character
140
+ # hash(Hash):: hash to merge
141
+ # name_value_delimiter(String):: name/value delimiter (defaults to '=')
142
+ #
143
+ # === Return
144
+ # hash(Hash):: merged hash result
145
+ def self.split_metadata(data, splitter, hash, name_value_delimiter = '=')
146
+ data.split(splitter).each do |pair|
147
+ name, value = pair.split(name_value_delimiter, 2)
148
+ hash[name.strip] = value.strip if name && value
149
+ end
150
+ hash
151
+ end
152
+
153
+ # Queries a whats-my-ip service for the public IP address of this instance.
154
+ # can query either for an expected IP or for any IP which is voted by majority
155
+ # (or unanimously). this is no guarantee that an instance actually has a public
156
+ # IP individually assigned to it as a private cloud instance will still appear
157
+ # to have it's router's public IP as it's own address.
158
+ #
159
+ # === Parameters
160
+ # options[:expected_ip](String):: expected IP address or nil (no DNS names)
161
+ # options[:unanimous][TrueClass|FalseClass]:: true if vote must be unanimous, false for simple majority of all responders
162
+ # options[:host_name](String):: host name for whats-my-ip query or DEFAULT_WHATS_MY_IP_HOST_NAME
163
+ # options[:logger](Logger):: logger or defaults to null logger
164
+ # options[:timeout][Fixnum]:: timeout in seconds or DEFAULT_WHATS_MY_IP_TIMEOUT
165
+ # options[:retry_delay][Fixnum]:: retry delay in seconds or DEFAULT_WHATS_MY_IP_RETRY_DELAY
166
+ #
167
+ # === Return
168
+ # public_ip(String):: the consensus public IP for this instance or nil
169
+ def self.query_whats_my_ip(options={})
170
+ expected_ip = options[:expected_ip]
171
+ raise ArgumentError.new("expected_ip is invalid") if expected_ip && !(expected_ip =~ IP_ADDRESS_REGEX)
172
+ unanimous = options[:unanimous] || false
173
+ host_name = options[:host_name] || DEFAULT_WHATS_MY_IP_HOST_NAME
174
+ logger = options[:logger] || Logger.new(::RightScale::Platform::Shell::NULL_OUTPUT_NAME)
175
+ timeout = options[:timeout] || DEFAULT_WHATS_MY_IP_TIMEOUT
176
+ retry_delay = options[:retry_delay] || DEFAULT_WHATS_MY_IP_RETRY_DELAY
177
+
178
+ if expected_ip
179
+ logger.info("Waiting for IP=#{expected_ip}")
180
+ else
181
+ logger.info("Waiting for any IP to converge.")
182
+ end
183
+
184
+ # attempt to dig some hosts.
185
+ hosts = `dig +short #{host_name}`.strip.split
186
+ if hosts.empty?
187
+ logger.info("No hosts to poll for IP from #{host_name}.")
188
+ else
189
+ # a little randomization avoids hitting the same hosts from each
190
+ # instance since there is no guarantee that the hosts are returned in
191
+ # random order.
192
+ hosts = hosts.sort { (rand(2) * 2) - 1 }
193
+ if logger.debug?
194
+ message = ["Using these hosts to check the IP:"]
195
+ hosts.each { |host| message << " #{host}" }
196
+ message << "-------------------------"
197
+ logger.debug(message.join("\n"))
198
+ end
199
+
200
+ unanimity = hosts.count
201
+ required_votes = unanimous ? unanimity : (1 + unanimity / 2)
202
+ logger.info("Required votes = #{required_votes}/#{unanimity}")
203
+ end_time = Time.now + timeout
204
+ loop do
205
+ reported_ips_to_voters = {}
206
+ address_to_hosts = {}
207
+ hosts.each do |host|
208
+ address = `curl --max-time 1 -S -s http://#{host}/ip/mine`.strip
209
+ logger.debug("Host=#{host} reports IP=#{address}")
210
+ address_to_hosts[address] ||= []
211
+ address_to_hosts[address] << host
212
+ if expected_ip
213
+ vote = (address_to_hosts[expected_ip] || []).count
214
+ popular_address = expected_ip
215
+ else
216
+ popular_address = nil
217
+ vote = 0
218
+ address_to_hosts.each do |address, hosts|
219
+ if hosts.count > vote
220
+ vote = hosts.count
221
+ popular_address = address
222
+ end
223
+ end
224
+ end
225
+ if vote >= required_votes
226
+ logger.info("IP=#{popular_address} has the required vote count of #{required_votes}.")
227
+ return popular_address
228
+ end
229
+ end
230
+
231
+ # go around again, if possible.
232
+ now_time = Time.now
233
+ break if now_time >= end_time
234
+ retry_delay = [retry_delay, end_time - now_time].min.to_i
235
+ logger.debug("Sleeping for #{retry_delay} seconds...")
236
+ sleep retry_delay
237
+ retry_delay = [retry_delay * 2, 60].min # a little backoff helps when launching thousands
238
+ end
239
+ logger.info("Never got the required vote count of #{required_votes}/#{unanimity} after #{timeout} seconds; public IP did not converge.")
240
+ return nil
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,106 @@
1
+ #
2
+ # Copyright (c) 2012 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
+ # Location for injected certificate
24
+ CERT_FILE = '/var/lib/waagent/Certificates.pem'
25
+
26
+ # Windows changes the ST=CA portion of our issuer name to S=CA at some point.
27
+ ISSUER_STATE_KEY = platform.windows? ? 'S' : 'ST'
28
+
29
+ # dependencies.
30
+ metadata_source 'metadata_sources/certificate_metadata_source'
31
+ metadata_writers 'metadata_writers/dictionary_metadata_writer',
32
+ 'metadata_writers/ruby_metadata_writer',
33
+ 'metadata_writers/shell_metadata_writer'
34
+
35
+ # set abbreviation for non-RS env var generation
36
+ abbreviation :waz
37
+
38
+ # Parses azure user metadata into a hash.
39
+ #
40
+ # === Parameters
41
+ # tree_climber(MetadataTreeClimber):: tree climber
42
+ # data(String):: raw data
43
+ #
44
+ # === Return
45
+ # result(Hash):: Hash-like leaf value
46
+ def create_user_metadata_leaf(tree_climber, data)
47
+ result = tree_climber.create_branch
48
+ ::RightScale::CloudUtilities.split_metadata(data.strip, '&', result)
49
+ result
50
+ end
51
+
52
+ # defaults.
53
+ default_option([:metadata_source, :user_metadata_cert_store], platform.windows? ? "cert:/LocalMachine/My" : CERT_FILE)
54
+ default_option([:metadata_source, :user_metadata_cert_issuer], "O=RightScale, C=US, #{ISSUER_STATE_KEY}=CA, CN=RightScale User Data")
55
+
56
+ default_option([:user_metadata, :metadata_tree_climber, :create_leaf_override], method(:create_user_metadata_leaf))
57
+
58
+ def wait_for_instance_ready
59
+ if platform.linux?
60
+ STDOUT.puts "Waiting for instance to appear ready."
61
+ until File.exist?(CERT_FILE) && File.mtime(CERT_FILE).to_f > platform.shell.booted_at.to_f do
62
+ sleep(1)
63
+ end
64
+ STDOUT.puts "Instance appears ready."
65
+ end
66
+ end
67
+
68
+ # Determines if the current instance is running on azure.
69
+ #
70
+ # === Return
71
+ # true if running on azure
72
+ def is_current_cloud?
73
+ # FIX: the presence of the user data cert isn't sufficient criteria to
74
+ # determine whether this is an azure instance. is there a mac address we can
75
+ # check against? in the meantime, just say no.
76
+ false
77
+ end
78
+
79
+ # Updates the given node with azure details.
80
+ #
81
+ # === Return
82
+ # always true
83
+ def update_details
84
+ details = {}
85
+ if ohai = @options[:ohai_node]
86
+ # FIX: there is currently no instance-facing API (i.e. an API which does not
87
+ # require management credentials) to provide the instance's public IP address
88
+ # so a workaround is required until the instance-facing API is available.
89
+ if public_ip = ::RightScale::CloudUtilities.query_whats_my_ip(:logger=>logger)
90
+ details[:public_ip] = public_ip
91
+ end
92
+ if platform.windows?
93
+ interface_names = ['Local Area Connection', # Windows Server 2008 R2
94
+ 'Ethernet'] # Windows Server 2012+ (?)
95
+ interface_names.each do |interface_name|
96
+ if ip = ::RightScale::CloudUtilities.ip_for_windows_interface(ohai, interface_name)
97
+ details[:private_ip] = ip
98
+ break
99
+ end
100
+ end
101
+ else
102
+ details[:private_ip] = ::RightScale::CloudUtilities.ip_for_interface(ohai, :eth0)
103
+ end
104
+ end
105
+ return details
106
+ end