Class: RightScale::Cloud

Inherits:
Object show all
Defined in:
lib/clouds/cloud.rb

Overview

Abstract base class for all clouds.

Defined Under Namespace

Classes: ActionResult, CloudError

Constant Summary collapse

WILDCARD =

wildcard used for some ‘all kinds’ selections.

:*
DEFAULT_CLOUD_METADATA_ROOT_PATH =

default tree-climber root paths for cloud/user metadata. they are basically placeholders for metadata sources which need to distinguish cloud from user but otherwise don’t use real root paths.

"cloud_metadata"
DEFAULT_USER_METADATA_ROOT_PATH =
"user_metadata"
DEFAULT_CLOUD_METADATA_FILE_PREFIX =

default writer output file prefixes are based on EC2 legacy files.

'meta-data'
DEFAULT_USER_METADATA_FILE_PREFIX =
'user-data'
RAW_METADATA_WRITER =

raw metadata writer is a special case and normally only invoked while metadata is being queried from source. it can also be referenced to read back the metadata in raw form.

:raw

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Cloud

Initializer.

Parameters

options(Hash)

options grab bag used to configure cloud and dependencies.

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
76
77
78
# File 'lib/clouds/cloud.rb', line 69

def initialize(options)
  raise ArgumentError.new("options[:name] is required") unless @name = options[:name]
  raise ArgumentError.new("options[:script_path] is required") unless @script_path = options[:script_path]

  # break options lineage and use Mash to handle keys as strings or tokens.
  # note that this is not a deep copy as :ohai is an option representing the
  # full ohai node in at least one use case.
  @options = Mash.new(options)
  @extended_clouds = []
end

Instance Attribute Details

#extended_cloudsObject (readonly)

Returns the value of attribute extended_clouds.



52
53
54
# File 'lib/clouds/cloud.rb', line 52

def extended_clouds
  @extended_clouds
end

#nameObject (readonly)

Returns the value of attribute name.



52
53
54
# File 'lib/clouds/cloud.rb', line 52

def name
  @name
end

#script_pathObject (readonly)

Returns the value of attribute script_path.



52
53
54
# File 'lib/clouds/cloud.rb', line 52

def script_path
  @script_path
end

Instance Method Details

#abbreviation(value = nil) ⇒ Object

Getter/setter for abbreviation which also sets default formatter options when an abbreviation is set.



117
118
119
120
121
122
123
# File 'lib/clouds/cloud.rb', line 117

def abbreviation(value = nil)
  unless value.to_s.empty?
    @abbreviation = value.to_s
    default_option([:cloud_metadata, :metadata_formatter, :formatted_path_prefix], "#{value.to_s.upcase}_")
  end
  @abbreviation
end

#build_metadata(kind) ⇒ Object

Executes a query for metadata and builds a metadata ‘tree’ according to the rules of provider and tree climber.

Parameters

kind(Token)

must be one of [:cloud_metadata, :user_metadata]

Return

metadata(Hash)

Hash-like metadata response



392
393
394
395
396
397
398
399
400
401
402
# File 'lib/clouds/cloud.rb', line 392

def (kind)
  @metadata_source_instance = create_dependency_type(kind, :metadata_source) unless @metadata_source_instance
   = create_dependency_type(kind, :metadata_tree_climber)
  provider = create_dependency_type(kind, :metadata_provider)
  provider.send(:metadata_source=, @metadata_source_instance)
  provider.send(:metadata_tree_climber=, )
  provider.send(:raw_metadata_writer=, (kind))

  # build
  return provider.send(:build_metadata)
end

#clear_stateObject

Attempts to clear any files generated by writers.

Return

always true

Raise

CloudError

on failure to clean state



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/clouds/cloud.rb', line 363

def clear_state
  output_dir_paths = []
  [:cloud_metadata, :user_metadata].each do |k|
    writers = create_dependency_type(k, :metadata_writers, WILDCARD)
    writers << (k)
    writers.each { |writer| output_dir_paths << writer.output_dir_path unless output_dir_paths.include?(writer.output_dir_path) }
  end
  last_exception = nil
  output_dir_paths.each do |output_dir_path|
    begin
      FileUtils.rm_rf(output_dir_path) if File.directory?(output_dir_path)
    rescue Exception => e
      last_exception = e
    end
  end
  fail(last_exception.message) if last_exception
  return ActionResult.new
rescue Exception => e
  return ActionResult.new(:exitstatus => 1, :error => "ERROR: #{e.message}")
end

#create_dependency_type(kind, category, dependency_type = nil) ⇒ Object

Creates the type using options specified by metadata kind, type category and specific type, if given.

Parameters

kind(Token)

must be one of [:cloud_metadata, :user_metadata]

category(Token)

category for dependency class

type(String|Token)

specific type or nil

Return

dependency(Object)

new instance of dependency class

Raises:

  • (NotImplementedError)


455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/clouds/cloud.rb', line 455

def create_dependency_type(kind, category, dependency_type = nil)
  # support wildcard case for all dependency types in a category.
  kind = kind.to_sym
  category = category.to_sym
  if WILDCARD == dependency_type
    types = self.send(category)
    return types.map { |type| create_dependency_type(kind, category, type) }
  end

  # get specific type from category on cloud, if necessary.
  dependency_type = self.send(category) unless dependency_type
  raise NotImplementedError.new("The #{name.inspect} cloud has not declared a #{category} type.") unless dependency_type
  dependency_type = dependency_type.to_s

  options = resolve_options(kind, category, dependency_type)
  dependency_class = resolve_dependency(dependency_type)
  return dependency_class.new(options)
end

#default_option(path, default_value) ⇒ Object

Merges the given default option at the given depth in the options hash but only if the value is not set. Handles subhash merging by giving the existing option key/value pairs precedence.

Parameters

path(Array|String)

path to option as an array of path elements or single

string which may contain forward slashes as element name delimiters.
default_value(String)

default value to conditionally insert/merge or nil



430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/clouds/cloud.rb', line 430

def default_option(path, default_value)
  # create subhashes to end of path.
  options = @options
  path = path.to_s.split('/') unless path.kind_of?(Array)
  path[0..-2].each { |child| options = options[child] ||= Mash.new }
  last_child = path[-1]

  # ensure any existing options override defaults.
  if default_value && options[last_child].respond_to?(:merge)
    options[last_child] = default_value.dup.merge(options[last_child])
  else
    options[last_child] ||= default_value
  end
end

#dependencies(*args) ⇒ Object

Runtime cloud depedencies (loaded on demand).



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/clouds/cloud.rb', line 137

def dependencies(*args)
  @dependencies ||= []
  args.each do |dependency_type|
    unless @dependencies.include?(dependency_type)
      # Just-in-time require new dependency
      resolve_dependency(dependency_type)
      @dependencies << dependency_type
    end
  end
  @dependencies
end

#dependency_base_paths(*args) ⇒ Object

Base paths for runtime cloud depedencies in order of priority. Defaults to location of cloud module files.



127
128
129
130
131
132
133
134
# File 'lib/clouds/cloud.rb', line 127

def dependency_base_paths(*args)
  @dependency_base_paths ||= []
  args.each do |path|
    path = relative_to_script_path(path)
    @dependency_base_paths << path unless @dependency_base_paths.include?(path)
  end
  @dependency_base_paths
end

#extend_cloud(cloud_name) ⇒ Object

Defines a base cloud type which the current instance extends. The base type is just-in-time evaluated into the current instance. The extended cloud must have been registered successfully.

Parameters

cloud_name(String|Token): name of cloud to extend

Return

always true

Raise

UnknownCloud

on failure to find extended cloud



194
195
196
197
198
199
200
201
202
203
# File 'lib/clouds/cloud.rb', line 194

def extend_cloud(cloud_name)
  cloud_name = CloudFactory.normalize_cloud_name(cloud_name)
  unless @extended_clouds.include?(cloud_name)
    @extended_clouds << cloud_name
    script_path = CloudFactory.instance.registered_script_path(cloud_name)
    text = File.read(script_path)
    self.instance_eval(text)
  end
  true
end

#extension_script_base_paths(*args) ⇒ Object

Base paths for external scripts which extend methods of cloud object. Names of scripts become instance methods and can override the predefined cloud methods. The factory defaults to using any scripts in “<rs_root_path>/bin/<cloud alias(es)>” directories.



209
210
211
212
213
214
215
216
# File 'lib/clouds/cloud.rb', line 209

def extension_script_base_paths(*args)
  @extension_script_base_paths ||= []
  args.each do |path|
    path = relative_to_script_path(path)
    @extension_script_base_paths << path unless @extension_script_base_paths.include?(path)
  end
  @extension_script_base_paths
end

#fail(message) ⇒ Object

Convenience method for failing to load or execute cloud definition.

Parameters

message(String)

message

Raise

always CloudError

Raises:



278
279
280
# File 'lib/clouds/cloud.rb', line 278

def fail(message)
  raise CloudError.new(message)
end

#finalize_default_optionsObject

Provides final default options after cloud definition(s) have had a chance to set defaults. defaults are first-come-first-served so none should be set by initialize() (as was the case in RightLink v5.7)



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/clouds/cloud.rb', line 83

def finalize_default_options
  # writer defaults.
  default_option([:metadata_writers, :output_dir_path], RightScale::AgentConfig.cloud_state_dir)
  default_option([:cloud_metadata, :metadata_writers, :file_name_prefix], DEFAULT_CLOUD_METADATA_FILE_PREFIX)
  default_option([:user_metadata, :metadata_writers, :file_name_prefix], DEFAULT_USER_METADATA_FILE_PREFIX)

  # metadata roots are referenced by both sources and tree climber, but
  # legacy RightLink v5.7 behavior was to treat them as separate categories
  # of options which had to be defaulted separately. for RightLink v5.8+
  # ensure cloud definitions only need to set metadata roots for sources
  # which require them.
   = option([:cloud_metadata, :metadata_tree_climber, :root_path]) ||
                             option([:metadata_source, :cloud_metadata_root_path]) ||
                             option(:cloud_metadata_root_path) ||
                             DEFAULT_CLOUD_METADATA_ROOT_PATH
  default_option([:cloud_metadata, :metadata_tree_climber, :root_path], )
  default_option([:metadata_source, :cloud_metadata_root_path], )
  default_option(:cloud_metadata_root_path, )  # uncategorized option, common to all types

   = option([:user_metadata, :metadata_tree_climber, :root_path]) ||
                            option([:metadata_source, :user_metadata_root_path]) ||
                            option(:user_metadata_root_path) ||
                            DEFAULT_USER_METADATA_ROOT_PATH
  default_option([:user_metadata, :metadata_tree_climber, :root_path], )
  default_option([:metadata_source, :user_metadata_root_path], )
  default_option(:user_metadata_root_path, )  # uncategorized option, common to all types
end

#is_current_cloud?Boolean

Determines if the current instance is running on the cloud indicated by this object.

Return

result(Boolean)

true if current cloud, false otherwise

Returns:

  • (Boolean)


257
258
259
# File 'lib/clouds/cloud.rb', line 257

def is_current_cloud?
  false  # clouds cannot self-detect without a specific implementation
end

#loggerObject

Syntatic sugar for options, which should always be valid under normal circumstances.



113
# File 'lib/clouds/cloud.rb', line 113

def logger; @options[:logger]; end

#metadata_formatter(type = nil) ⇒ Object

Dependency type for metadata formatter



219
220
221
222
# File 'lib/clouds/cloud.rb', line 219

def (type = nil)
  dependencies(type) if type
  @metadata_formatter ||= type || :metadata_formatter
end

#metadata_provider(type = nil) ⇒ Object

Dependency type for metadata provider



225
226
227
228
# File 'lib/clouds/cloud.rb', line 225

def (type = nil)
  dependencies(type) if type
  @metadata_provider ||= type || :metadata_provider
end

#metadata_source(type = nil) ⇒ Object

Dependency type for metadata source



231
232
233
234
# File 'lib/clouds/cloud.rb', line 231

def (type = nil)
  dependencies(type) if type
  @metadata_source ||= type || :metadata_source
end

#metadata_tree_climber(type = nil) ⇒ Object

Dependency type for metadata tree climber



237
238
239
240
# File 'lib/clouds/cloud.rb', line 237

def (type = nil)
  dependencies(type) if type
  @metadata_tree_climber ||= type || :metadata_tree_climber
end

#metadata_writers(*args) ⇒ Object

Dependency type for metadata writers. Note that the raw writer is automatic (writes raw responses using relative paths while data is being queried).



245
246
247
248
249
250
# File 'lib/clouds/cloud.rb', line 245

def (*args)
  dependencies(*args)
  @metadata_writers ||= []
  args.each { || @metadata_writers <<  unless @metadata_writers.include?() }
  @metadata_writers
end

#option(path) ⇒ Object

Gets the option given by path, if it exists.

Parameters

path(Array|String)

path to option as an array of path elements or single

string which may contain forward slashes as element name delimiters.
default_value(String)

default value to conditionally insert/merge or nil

Return

result(Object)

existing option or nil



413
414
415
416
417
418
419
420
# File 'lib/clouds/cloud.rb', line 413

def option(path)
  options = @options
  path = path.to_s.split('/') unless path.kind_of?(Array)
  path[0..-2].each do |child|
    return nil unless (options = options[child]) && options.respond_to?(:has_key?)
  end
  options[path[-1]]
end

#platformObject

Convenience method for getting information about the current machine platform.

Return

result(Boolean)

true if windows



287
288
289
# File 'lib/clouds/cloud.rb', line 287

def platform
  ::RightScale::Platform
end

#read_cloud_metadata(writer_type = RAW_METADATA_WRITER, subpath = nil) ⇒ Object

Convenience method for reading only cloud metdata.



345
# File 'lib/clouds/cloud.rb', line 345

def (writer_type = RAW_METADATA_WRITER, subpath = nil); (:cloud_metadata, writer_type, subpath); end

#read_metadata(kind = :user_metadata, writer_type = RAW_METADATA_WRITER, subpath = nil) ⇒ Object

Reads the generated metadata file of the given kind and writer type.

Parameters

kind(Symbol)

kind of metadata must be one of [:cloud_metadata, :user_metadata]

writer_type(Symbol)

writer_type [RAW_METADATA_WRITER, …]

Return

result(ActionResult)

action result



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/clouds/cloud.rb', line 299

def (kind = :user_metadata, writer_type = RAW_METADATA_WRITER, subpath = nil)
  kind = kind.to_sym
  writer_type = writer_type.to_sym
  if RAW_METADATA_WRITER == writer_type
    reader = (kind)
  else
    reader = create_dependency_type(kind, :metadata_writers, writer_type)
  end
  output = reader.read(subpath)
  return ActionResult.new(:output => output)
rescue Exception => e
  return ActionResult.new(:exitstatus => 1, :error => "ERROR: #{e.message}")
end

#read_user_metadata(writer_type = RAW_METADATA_WRITER, subpath = nil) ⇒ Object

Convenience method for reading only cloud metdata.



348
# File 'lib/clouds/cloud.rb', line 348

def (writer_type = RAW_METADATA_WRITER, subpath = nil); (:user_metadata, writer_type, subpath); end

#resolve_dependency(dependency_type) ⇒ Object

Just-in-time requires a cloud’s dependency, which should include its relative location (and sub-type) in the dependency name (e.g. ‘metadata_sources/http_metadata_source’ => Sources::HttpMetadataSource). the dependency can also be in the RightScale module namespace because it begin evaluated there.

note that actual instantiation of the dependency is on-demand from the cloud type.

Parameters

dependency_type(String|Token)

snake-case name for dependency type

Return

dependency(Class)

resolved dependency class



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/clouds/cloud.rb', line 163

def resolve_dependency(dependency_type)
  dependency_class_name = dependency_type.to_s.camelize
  begin
    dependency_class = Class.class_eval(dependency_class_name)
  rescue NameError
    search_paths = (dependency_base_paths || []) + [File.dirname(__FILE__)]
    dependency_file_name = dependency_type + ".rb"
    search_paths.each do |search_path|
      file_path = File.normalize_path(File.join(search_path, dependency_file_name))
      if File.file?(file_path)
        require File.normalize_path(File.join(search_path, dependency_type))
        break
      end
    end
    dependency_class = Class.class_eval(dependency_class_name)
  end
  dependency_class
end

#update_detailsObject

Updates the given node with any cloud-specific detailed information. Adds nothing by default. The ohai node can be retreived as option(:ohai) and any details can be added to the option(:ohai) node.

Return

always true



267
268
269
# File 'lib/clouds/cloud.rb', line 267

def update_details
  {}
end

#write_cloud_metadataObject

Convenience method for writing only cloud metdata.



351
# File 'lib/clouds/cloud.rb', line 351

def ; (:cloud_metadata); end

#write_metadata(kind = WILDCARD) ⇒ Object

Queries and writes current metadata to file.

Parameters

kind(Symbol)

kind of metadata must be one of [:cloud_metadata, :user_metadata, WILDCARD]

Return

result(ActionResult)

action result



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/clouds/cloud.rb', line 320

def (kind = WILDCARD)
  kind = kind.to_sym
  kinds = [:cloud_metadata, :user_metadata].select { |k| WILDCARD == kind || k == kind }
  kinds.each do |k|
    formatter = create_dependency_type(k, :metadata_formatter)
    writers = create_dependency_type(k, :metadata_writers, WILDCARD)
     = (k)
    unless .empty?
       = formatter.()
      writers.each { |writer| writer.write() }
    end
  end
  return ActionResult.new
rescue Exception => e
  return ActionResult.new(:exitstatus => 1, :error => "ERROR: #{e.message}")
ensure
  # release metadata source after querying all metadata.
  if @metadata_source_instance
     = @metadata_source_instance
    @metadata_source_instance = nil
    .finish
  end
end

#write_user_metadataObject

Convenience method for writing only user metdata.



354
# File 'lib/clouds/cloud.rb', line 354

def ; (:user_metadata); end