Class: CloudMaker::Config
- Inherits:
-
Object
- Object
- CloudMaker::Config
- Defined in:
- lib/cloud_maker/config.rb
Defined Under Namespace
Classes: ContentNotFound, FileContentNotFound, GitHubContentNotFound, HTTPContentNotFound
Constant Summary collapse
- CLOUD_CONFIG_HEADER =
Internal: A mime header for the Cloud Init config section of the user data
%Q|Content-Type: text/cloud-config; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename="cloud-config.yaml"\n\n|
- INCLUDES_HEADER =
Internal: A mime header for the includes section of the user data
%Q|Content-Type: text/x-include-url; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename="includes.txt"\n\n|
- BOOTHOOK_HEADER =
Internal: A mime header for the Cloud Boothook section of the user data
%Q|Content-Type: text/cloud-boothook; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename="boothook.sh"\n\n|
- MULTIPART_HEADER =
Internal: A multipart mime header for describing the entire user data content. It includes a placeholder for the boundary text ‘_boundary___’ that needs to be replaced in the actual mime document.
%Q|Content-Type: multipart/mixed; boundary="___boundary___"\nMIME-Version: 1.0\n\n|
- DEFAULT_KEY_PROPERTIES =
Internal: If you don’t specify a property associated with a key in the cloud_maker config file we will use these properties to fill in the blanks
{ "environment" => true, "required" => false, "value" => nil, "default" => nil, "description" => nil }
Instance Attribute Summary collapse
-
#cloud_config ⇒ Object
Public: Gets/Sets the Hash of Cloud Init properties.
-
#extra_options ⇒ Object
Public: Gets/Sets extra information about the config to be stored for archival purposes.
-
#imports ⇒ Object
Internal: Gets/Sets an array of paths to other cloud maker configs to import.
-
#includes ⇒ Object
Public: Gets/Sets an Array of URLs to be included, this corresponds to the list of URLs in a Cloud Init includes file.
-
#options ⇒ Object
Public: Gets/Sets the CloudMaker specific properties.
Class Method Summary collapse
-
.from_yaml(instance_config_yaml, options = {}) ⇒ Object
Public: Takes the path of a YAML file and loads a new Config object from it.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Public: Access values in the cloudmaker options object.
-
#[]=(key, val) ⇒ Object
Public: Sets the value property for key in the cloudmaker options hash.
-
#advanced_config?(value) ⇒ Boolean
Internal: Determines if value should be treated as a value or a property configuration hash.
-
#boothook_data ⇒ Object
Public: Generates a shell script to set environment variables for all CloudMaker config options, will get executed at machine boot.
-
#cloud_config_data ⇒ Object
Public: Generates a cloud-init configuration.
- #config_name ⇒ Object
-
#extract_cloudmaker_config!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the CloudMaker specific portions of it.
-
#extract_imports!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the imports list.
-
#extract_includes!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the includes list.
-
#import(cloud_maker_config) ⇒ Object
Internal: Deep merges another CloudMaker::Config into this one giving precedence to all values set in this one.
-
#includes_data ⇒ Object
Public: Generates a cloud-init includes list.
-
#initialize(cloud_config, extra_options = {}) ⇒ Config
constructor
Public: Initializes a new CloudMaker object.
-
#inspect ⇒ Object
Returns a String representation of the CloudMaker config.
- #method_missing(method = nil, *args) ⇒ Object
-
#missing_values ⇒ Object
Public: Finds a list of keys in the CloudMaker config that are required to have a value but do not yet have one.
-
#set_environment_variable_cmd(key, value) ⇒ Object
Internal: Generates the shell command necessary to set an environment variable.
-
#to_hash ⇒ Object
Returns a Hash of all of the CloudMaker specific properties in the configuration.
-
#to_user_data ⇒ Object
Public: Generates a multipart userdata string suitable for use with Cloud Init on EC2.
-
#valid? ⇒ Boolean
Public: Check if the CloudMaker config is in a valid state.
Constructor Details
#initialize(cloud_config, extra_options = {}) ⇒ Config
Public: Initializes a new CloudMaker object.
cloud_config - A Hash describing all properties of the CloudMaker config.
'cloud_maker' - The configuration properties for CloudMaker. These can
be specified either as
key: value
or as
key: {
environment: boolean,
required: boolean,
value: value
}
If specified as key: value DEFAULT_KEY_PROPERTIES will
be used. If the detailed version is used all properties
are optional and DEFAULT_KEY_PROPERTIES will be used to
fill in the blanks.
'include' - An array of URLs or a String containing 1 URL per line
with optional # prefixed lines as comments.
... - All valid properties of a Cloud Init config
are also valid here. See:
https://help.ubuntu.com/community/CloudInit
extra_options - Options that describe the config as opposed to being part
of the config.
'config_path' - The path the config was loaded from. Used for archival purposes.
'import_ec2' - CloudMaker::EC2 defines properties it relies on, if this value
is true then we pull those property definitions in.
Returns a CloudMaker object
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/cloud_maker/config.rb', line 91 def initialize(cloud_config, ={}) self. = cloud_config = cloud_config.dup self. = extract_cloudmaker_config!(cloud_config) self.includes = extract_includes!(cloud_config) self.imports = extract_imports!(cloud_config) self.cloud_config = cloud_config self.import(self.class.new(EC2::CLOUD_MAKER_CONFIG, 'config_path' => "EC2")) if (['import_ec2']) # It's important here that reverse duplicates the imports array as executing the import will # add the imported configs imports to the list and we do NOT want to reimport those as well. self.imports.reverse.each do |import_path| self.import(self.class.from_yaml(import_path, self..merge('import_ec2' => false))) end self['tags'] ||= {} self['tags']['cloud_maker_config'] = self.config_name end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method = nil, *args) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/cloud_maker/config.rb', line 111 def method_missing(method=nil, *args) if method.to_s[-1] == "?" key_name = method.to_s[0, method.to_s.length-1] value = self[key_name] if value.respond_to? :empty? !value.empty? else !value.nil? end else super end end |
Instance Attribute Details
#cloud_config ⇒ Object
Public: Gets/Sets the Hash of Cloud Init properties. See help.ubuntu.com/community/CloudInit for valid options
31 32 33 |
# File 'lib/cloud_maker/config.rb', line 31 def cloud_config @cloud_config end |
#extra_options ⇒ Object
Public: Gets/Sets extra information about the config to be stored for archival purposes.
37 38 39 |
# File 'lib/cloud_maker/config.rb', line 37 def @extra_options end |
#imports ⇒ Object
Internal: Gets/Sets an array of paths to other cloud maker configs to import.
39 40 41 |
# File 'lib/cloud_maker/config.rb', line 39 def imports @imports end |
#includes ⇒ Object
Public: Gets/Sets an Array of URLs to be included, this corresponds to the list of URLs in a Cloud Init includes file.
34 35 36 |
# File 'lib/cloud_maker/config.rb', line 34 def includes @includes end |
#options ⇒ Object
Public: Gets/Sets the CloudMaker specific properties. The options hash is formatted as:
{
'key1' => {
'environment': boolean,
'required': boolean,
'value': value
},
'key2' => { ... },
...
}
28 29 30 |
# File 'lib/cloud_maker/config.rb', line 28 def @options end |
Class Method Details
.from_yaml(instance_config_yaml, options = {}) ⇒ Object
Public: Takes the path of a YAML file and loads a new Config object from it.
instance_config_yaml - The path of the YAML file options - Any options to pass through as options to CloudMaker::Config::initialize
Returns a new Config Raises: GitHubContentNotFound, HTTPContentNotFound, or FileContentNotFound if the file doesn’t exist. Raises: SyntaxError if the YAML file is invalid.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/cloud_maker/config.rb', line 259 def from_yaml(instance_config_yaml, ={}) if instance_config_yaml =~ /\Agithub:\/\// begin user, repo, *path = instance_config_yaml.split('/')[2..-1] begin if ['github_token'] response = RestClient.get( "https://api.github.com/repos/#{user}/#{repo}/contents/#{path.join('/')}", "Authorization" => "token #{['github_token']}" ) else response = RestClient.get("https://api.github.com/repos/#{user}/#{repo}/contents/#{path.join('/')}") end cloud_yaml = Base64.decode64(JSON.parse(response)['content']) rescue raise GitHubContentNotFound.new( "Unable to access the configuration #{instance_config_yaml} from GitHub.", instance_config_yaml ) end end elsif instance_config_yaml =~ /\Ahttps?:\/\// begin cloud_yaml = RestClient.get(instance_config_yaml) rescue raise HTTPContentNotFound.new("Unable to access the configuration via HTTP from #{instance_config_yaml}.", instance_config_yaml) end else begin instance_config_yaml = File.(instance_config_yaml) cloud_yaml = File.open(instance_config_yaml, "r") #Right_AWS will base64 encode this for us rescue raise FileContentNotFound.new("Unable to access the configuration via your local file system from #{instance_config_yaml}.", instance_config_yaml) end end # loading a blank config file returns false, it's an odd degenerate case but handling # it like this makes sanity checking other missing values easy. config = YAML::load(cloud_yaml) || {} CloudMaker::Config.new(config, .merge('config_path' => instance_config_yaml)) end |
Instance Method Details
#[](key) ⇒ Object
Public: Access values in the cloudmaker options object
key - The key of the property you’re accessing
Returns the value property for options
224 225 226 |
# File 'lib/cloud_maker/config.rb', line 224 def [](key) self.[key] ? self.[key]["value"] : nil end |
#[]=(key, val) ⇒ Object
Public: Sets the value property for key in the cloudmaker options hash
key - The key of the property you’re accessing val - The value you wish to assign to the key
Returns val
234 235 236 237 238 239 240 241 |
# File 'lib/cloud_maker/config.rb', line 234 def []=(key, val) if (self.[key]) self.[key]["value"] = val else self.[key] = DEFAULT_KEY_PROPERTIES.merge('value' => val) end val end |
#advanced_config?(value) ⇒ Boolean
Internal: Determines if value should be treated as a value or a property configuration hash. A property configuration hash would specify at least one of environment, value, or required.
value - The value to evaluate
Returns true if value is a property configuration, and false if it’s just
a value
341 342 343 |
# File 'lib/cloud_maker/config.rb', line 341 def advanced_config?(value) value.kind_of?(Hash) && !(DEFAULT_KEY_PROPERTIES.keys & value.keys).empty? end |
#boothook_data ⇒ Object
Public: Generates a shell script to set environment variables for all CloudMaker config options, will get executed at machine boot.
Returns a String containing the shell script
197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/cloud_maker/config.rb', line 197 def boothook_data env_run_cmds = [] self..each_pair do |key, properties| if properties["environment"] && !properties["value"].nil? env_run_cmds.push(set_environment_variable_cmd(key, properties["value"])) end end env_run_cmds.push("touch /var/lib/cloud/cloud_init_complete") return "#cloud-boothook\n#!/bin/sh\nif [ ! -f /var/lib/cloud/cloud_init_complete ]; then\n#{env_run_cmds.join("\n")}\nfi\n" end |
#cloud_config_data ⇒ Object
Public: Generates a cloud-init configuration
Returns a String containing the cloud init configuration in YAML format
189 190 191 |
# File 'lib/cloud_maker/config.rb', line 189 def cloud_config_data return "#cloud-config\n#{self.cloud_config.to_yaml}" end |
#config_name ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/cloud_maker/config.rb', line 125 def config_name files = self.imports.dup files.push self.['config_path'] if self.['config_path'] files.reverse.map { |import| if import.rindex('/') import = import[import.rindex('/')+1..-1] end import.gsub(/\..*/, '').gsub(/[^\w]/, '-') }.join(':') end |
#extract_cloudmaker_config!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the CloudMaker specific portions of it. For each key/value it fills in any property blanks from the DEFAULT_KEY_PROPERTIES. It also deletes the cloud_maker property from config.
config - A hash that should contain a ‘cloud_maker’ key storing CloudMaker
configuration properties.
Returns a Hash in the format of
{'key1' => {
'environment' => ...,
'value' => ...,
'required' ...
}, 'key2' => ... , ...}
318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/cloud_maker/config.rb', line 318 def extract_cloudmaker_config!(config) cloud_maker_config = config.delete('cloud-maker') || {} cloud_maker_config.keys.each do |key| #if key is set to anything but a hash then we treat it as the value property if !advanced_config?(cloud_maker_config[key]) cloud_maker_config[key] = { "value" => cloud_maker_config[key] } end cloud_maker_config[key] = DEFAULT_KEY_PROPERTIES.merge(cloud_maker_config[key]) end cloud_maker_config end |
#extract_imports!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the imports list.
config - A hash that should contain an ‘import’ key storing an array of paths to import.
Returns an Array of URLs
374 375 376 |
# File 'lib/cloud_maker/config.rb', line 374 def extract_imports!(config) config.delete('import') || [] end |
#extract_includes!(config) ⇒ Object
Internal: Takes a CloudMaker config and parses out the includes list. If the list is an array it treats each entry as a URL. If it is a string it treats the string as the contents of a Cloud Init include file.
config - A hash that should contain an ‘include’ key storing the include information.
Returns an Array of URLs
353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/cloud_maker/config.rb', line 353 def extract_includes!(config) includes = config.delete('include') #if we didn't specify it just use a blank array if includes.nil? includes = [] #if we passed something other than an array turn it into a string and split it up into urls elsif !includes.kind_of?(Array) includes = includes.to_s.split("\n") includes.reject! {|line| line.strip[0] == "#" || line.strip.empty?} end includes end |
#import(cloud_maker_config) ⇒ Object
Internal: Deep merges another CloudMaker::Config into this one giving precedence to all values set in this one. Arrays will be merged as imported_array.concat(current_array).uniq.
“–” can be used as a knockout prefix, ie. “–foo” will delete the key “foo” when an array is merged, or “–” will delete the entire existing contents of the array.
It should be noted that this is not reference safe, ie. objects within cloud_maker_config will end up referenced from this config object as well.
cloud_maker_config - The CloudMaker::Config to be imported
Returns nothing.
426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/cloud_maker/config.rb', line 426 def import(cloud_maker_config) [:options, :includes, :imports, :cloud_config, :extra_options].each do |key| key = key.to_sym #toss both of these into a hash because deep_merge only works on hashes cloud_maker_config_hash = {:value => cloud_maker_config.send(key)} self_hash = {:value => self.send(key)} self.send( :"#{key}=", cloud_maker_config_hash.deep_merge!( self_hash, :preserve_unmergeables => false, :knockout_prefix => '--' )[:value] ) end end |
#includes_data ⇒ Object
Public: Generates a cloud-init includes list
Returns a String containing the cloud init includes list
214 215 216 |
# File 'lib/cloud_maker/config.rb', line 214 def includes_data ["#include", *self.includes.map(&:to_s)].join("\n") end |
#inspect ⇒ Object
Returns a String representation of the CloudMaker config
244 245 246 |
# File 'lib/cloud_maker/config.rb', line 244 def inspect "CloudMakerConfig#{self..inspect}" end |
#missing_values ⇒ Object
Public: Finds a list of keys in the CloudMaker config that are required to have a value but do not yet have one.
Returns an Array of required keys that are missing values.
149 150 151 |
# File 'lib/cloud_maker/config.rb', line 149 def missing_values self..select {|key, option| (option["required"] || option["default"]) && option["value"].nil?}.map(&:first).map(&:dup) end |
#set_environment_variable_cmd(key, value) ⇒ Object
Internal: Generates the shell command necessary to set an environment variable. It escapes the value but assumes there are no special characters in the key. If value is an array or a hash it generates an environment variable for each value in the array/hash with the key set to key_index.
key - The key of the environment variable value - The value to set the key to
Returns a string that can be executed to globally set the environment variable.
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/cloud_maker/config.rb', line 387 def set_environment_variable_cmd(key, value) if (value.kind_of?(Hash)) strings = [] value.keys.each { |hash_key| strings.push(set_environment_variable_cmd("#{key}_#{hash_key}", value[hash_key])) } strings.push(set_environment_variable_cmd("#{key}_length", value.keys.join(' '))) strings.push(set_environment_variable_cmd("#{key}_json", value.to_json)) strings.join(';') elsif (value.kind_of?(Array)) strings = [] value.each_with_index { |arr_val, i| strings.push(set_environment_variable_cmd("#{key}_#{i}", arr_val)) } strings.push(set_environment_variable_cmd("#{key}_length", value.length)) strings.push(set_environment_variable_cmd("#{key}_json", value.to_json)) strings.join(';') else underscored_key = key.to_s.gsub(/[^a-zA-Z0-9_]/, '_') escaped_value = value.to_s.gsub(/"/, '\\\\\\\\\"') "echo \"#{underscored_key}=\\\"#{escaped_value}\\\"\" >> /etc/environment" end end |
#to_hash ⇒ Object
Returns a Hash of all of the CloudMaker specific properties in the configuration.
154 155 156 157 158 159 160 161 162 |
# File 'lib/cloud_maker/config.rb', line 154 def to_hash { 'cloud-maker' => self..map {|key, properties| [key, properties["value"]]}, 'cloud-init' => self.cloud_config, 'include' => self.includes, 'import' => self.imports, 'extra-options' => self. } end |
#to_user_data ⇒ Object
Public: Generates a multipart userdata string suitable for use with Cloud Init on EC2
Returns a String containing the mime encoded userdata
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/cloud_maker/config.rb', line 167 def to_user_data # build a multipart document parts = [] parts.push(BOOTHOOK_HEADER + boothook_data) parts.push(CLOUD_CONFIG_HEADER + cloud_config_data) parts.push(INCLUDES_HEADER + includes_data) #not that it's likely but lets make sure that we don't choose a boundary that exists in the document. boundary = '' while parts.any? {|part| part.index(boundary)} boundary = "===============#{rand(8999999999999999999) + 1000000000000000000}==" end header = MULTIPART_HEADER.sub(/___boundary___/, boundary) return [header, *parts].join("\n--#{boundary}\n") + "\n--#{boundary}--" end |
#valid? ⇒ Boolean
Public: Check if the CloudMaker config is in a valid state.
Returns true if and only if all required properties have non-nil values
and false otherwise.
141 142 143 |
# File 'lib/cloud_maker/config.rb', line 141 def valid? self..all? {|key, option| !(option["required"] && option["value"].nil?)} end |