cloud-maker 0.0.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.
data/bin/cloud-maker ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ require 'thor'
3
+ require 'colorize'
4
+ require 'cloud-maker'
5
+ # require 'pry'
6
+
7
+ class CloudMakerCLI < Thor
8
+ include Thor::Actions
9
+
10
+ desc "launch [INSTANCE_CONFIG_YAML]", "Launch a new EC2 instance as described by INSTANCE_CONFIG_YAML"
11
+ method_option :aws_access_key_id,
12
+ :desc => "Your AWS access key id",
13
+ :default => ENV['AWS_ACCESS_KEY_ID'],
14
+ :required => true
15
+ method_option :aws_secret_access_key,
16
+ :desc => "Your AWS secret access key",
17
+ :default => ENV['AWS_SECRET_ACCESS_KEY'],
18
+ :required => true
19
+ method_option :set,
20
+ :alias => '-s',
21
+ :type => :hash,
22
+ :default => {},
23
+ :desc => "Set parameters in the CloudMaker config"
24
+ def launch(instance_config_yaml)
25
+ puts "---------------------------"
26
+ puts "Launching new EC2 instance"
27
+ puts "---------------------------\n"
28
+
29
+ config = CloudMaker::Config.from_yaml(instance_config_yaml)
30
+ options.set.each_pair {|key, val| config[key] = val}
31
+
32
+ if !config.valid?
33
+ say "Before an instance can be launched we need a few more values to be specified.".yellow
34
+ say "Currently missing: #{config.missing_values.map{|key| key.cyan}.join(', ')}"
35
+ puts
36
+
37
+ config.missing_values.each do |key|
38
+ config[key] = ENV[key] if ENV[key]
39
+ end
40
+
41
+ config.missing_values.each do |key|
42
+ if (config.options[key]["description"])
43
+ say config.options[key]["description"]
44
+ end
45
+ config[key] = ask "Please choose a value for #{key.cyan}: "
46
+ end
47
+ end
48
+
49
+ cloud_maker = CloudMaker::Ec2.new(config,
50
+ :aws_access_key_id => options.aws_access_key_id,
51
+ :aws_secret_access_key => options.aws_secret_access_key
52
+ )
53
+
54
+ if (!config.includes.empty?)
55
+ puts
56
+ say 'Include URLs:'.green
57
+ config.includes.each do |url|
58
+ puts url
59
+ end
60
+ end
61
+
62
+ puts
63
+ say "CloudMaker configuration:".green
64
+ print_table config.to_hash.map {|key, val| [key.dup.cyan, val]}
65
+
66
+ puts
67
+ say "Configuration file:".green + " " + config.extra_options[:config_path]
68
+ puts
69
+
70
+ if yes?("Launch a new EC2 instance with the options above? (y/n)")
71
+ instance = cloud_maker.launch
72
+ puts
73
+ say "Successfully launched new EC2 instance: ".green + instance[:aws_instance_id].magenta
74
+ puts
75
+ print_table instance.map {|key, val|
76
+ [key.to_s.cyan, val.to_s]
77
+ }
78
+ else
79
+ say "Launch aborted!".red
80
+ end
81
+ end
82
+
83
+ desc "scratch", "Play with Thor"
84
+ def scratch(arg1)
85
+ say 'hello'
86
+ say 'hello in red'.red
87
+ end
88
+ end
89
+
90
+ CloudMakerCLI.start
@@ -0,0 +1,4 @@
1
+ require 'cloud_maker/config'
2
+ require 'cloud_maker/ec2'
3
+ require 'cloud_maker/s3_archiver'
4
+ require 'cloud_maker/shell_executor'
@@ -0,0 +1,302 @@
1
+ require 'yaml'
2
+ require 'stringio'
3
+
4
+ module CloudMaker
5
+ class Config
6
+ # Public: Gets/Sets the CloudMaker specific properties. The options hash
7
+ # is formatted as:
8
+ # {
9
+ # 'key1' => {
10
+ # 'environment': boolean,
11
+ # 'required': boolean,
12
+ # 'value': value
13
+ # },
14
+ # 'key2' => { ... },
15
+ # ...
16
+ # }
17
+ attr_accessor :options
18
+ # Public: Gets/Sets the Hash of Cloud Init properties. See
19
+ # https://help.ubuntu.com/community/CloudInit for valid options
20
+ attr_accessor :cloud_config
21
+ # Public: Gets/Sets an Array of URLs to be included, this corresponds to the
22
+ # list of URLs in a Cloud Init includes file.
23
+ attr_accessor :includes
24
+ # Public: Gets/Sets extra information about the config to be stored for
25
+ # archival purposes.
26
+ attr_accessor :extra_options
27
+
28
+ # Internal: A mime header for the Cloud Init config section of the user data
29
+ CLOUD_CONFIG_HEADER = %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|
30
+ # Internal: A mime header for the includes section of the user data
31
+ INCLUDES_HEADER = %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|
32
+ # Internal: A mime header for the Cloud Boothook section of the user data
33
+ BOOTHOOK_HEADER = %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|
34
+ # Internal: A multipart mime header for describing the entire user data
35
+ # content. It includes a placeholder for the boundary text '___boundary___'
36
+ # that needs to be replaced in the actual mime document.
37
+ MULTIPART_HEADER = %Q|Content-Type: multipart/mixed; boundary="___boundary___"\nMIME-Version: 1.0\n\n|
38
+
39
+ # Internal: If you don't specify a property associated with a key in the
40
+ # cloud_maker config file we will use these properties to fill in the blanks
41
+ DEFAULT_KEY_PROPERTIES = {
42
+ "environment" => true,
43
+ "required" => false,
44
+ "value" => nil
45
+ }
46
+
47
+ # Public: Initializes a new CloudMaker object.
48
+ #
49
+ # cloud_config - A Hash describing all properties of the CloudMaker config.
50
+ # 'cloud_maker' - The configuration properties for CloudMaker. These can
51
+ # be specified either as
52
+ # key: value
53
+ # or as
54
+ # key: {
55
+ # environment: boolean,
56
+ # required: boolean,
57
+ # value: value
58
+ # }
59
+ # If specified as key: value DEFAULT_KEY_PROPERTIES will
60
+ # be used. If the detailed version is used all properties
61
+ # are optional and DEFAULT_KEY_PROPERTIES will be used to
62
+ # fill in the blanks.
63
+ #
64
+ # 'include' - An array of URLs or a String containing 1 URL per line
65
+ # with optional # prefixed lines as comments.
66
+ # ... - All valid properties of a Cloud Init config
67
+ # are also valid here. See:
68
+ # https://help.ubuntu.com/community/CloudInit
69
+ # extra_options - Extra information about the instantiation. These will not
70
+ # be used to launch the instance but will be stored in the
71
+ # archive describing the instance.
72
+ #
73
+ # Returns a CloudMaker object
74
+ def initialize(cloud_config, extra_options)
75
+ self.extra_options = extra_options
76
+ cloud_config = cloud_config.dup
77
+ self.options = extract_cloudmaker_config!(cloud_config)
78
+ self.includes = extract_includes!(cloud_config)
79
+ self.cloud_config = cloud_config
80
+ end
81
+
82
+ # Public: Check if the CloudMaker config is in a valid state.
83
+ #
84
+ # Returns true if and only if all required properties have non-nil values
85
+ # and false otherwise.
86
+ def valid?
87
+ self.options.all? {|key, option| !(option["required"] && option["value"].nil?)}
88
+ end
89
+
90
+ # Public: Finds a list of keys in the CloudMaker config that are required to
91
+ # have a value but do not yet have one.
92
+ #
93
+ # Returns an Array of required keys that are missing values.
94
+ def missing_values
95
+ self.options.select {|key, option| option["required"] && option["value"].nil?}.map(&:first).map(&:dup)
96
+ end
97
+
98
+ # Returns a Hash of all of the CloudMaker specific properties in the configuration.
99
+ def to_hash
100
+ self.options.map {|key, properties| [key, properties["value"]]}
101
+ end
102
+
103
+ # Public: Generates a multipart userdata string suitable for use with Cloud Init on EC2
104
+ #
105
+ # Returns a String containing the mime encoded userdata
106
+ def to_user_data
107
+ # build a multipart document
108
+ parts = []
109
+
110
+ parts.push(BOOTHOOK_HEADER + boothook_data)
111
+ parts.push(CLOUD_CONFIG_HEADER + cloud_config_data)
112
+ parts.push(INCLUDES_HEADER + includes_data)
113
+
114
+ #not that it's likely but lets make sure that we don't choose a boundary that exists in the document.
115
+ boundary = ''
116
+ while parts.any? {|part| part.index(boundary)}
117
+ boundary = "===============#{rand(8999999999999999999) + 1000000000000000000}=="
118
+ end
119
+
120
+ header = MULTIPART_HEADER.sub(/___boundary___/, boundary)
121
+
122
+ return [header, *parts].join("\n--#{boundary}\n") + "\n--#{boundary}--"
123
+ end
124
+
125
+ # Public: Generates a cloud-init configuration
126
+ #
127
+ # Returns a String containing the cloud init configuration in YAML format
128
+ def cloud_config_data
129
+ return "#cloud-config\n#{self.cloud_config.to_yaml}"
130
+ end
131
+
132
+ # Public: Generates a shell script to set environment variables for all
133
+ # CloudMaker config options, will get executed at machine boot.
134
+ #
135
+ # Returns a String containing the shell script
136
+ def boothook_data
137
+ env_run_cmds = []
138
+
139
+ self.options.each_pair do |key, properties|
140
+ if properties["environment"] && !properties["value"].nil?
141
+ env_run_cmds.push(set_environment_variable_cmd(key, properties["value"]))
142
+ end
143
+ end
144
+
145
+ return "#cloud-boothook\n#!/bin/sh\n#{env_run_cmds.join("\n")}\n"
146
+ end
147
+
148
+
149
+ # Public: Generates a cloud-init includes list
150
+ #
151
+ # Returns a String containing the cloud init includes list
152
+ def includes_data
153
+ ["#include", *self.includes.map(&:to_s)].join("\n")
154
+ end
155
+
156
+
157
+ # Public: Access values in the cloudmaker options object
158
+ #
159
+ # key - The key of the property you're accessing
160
+ #
161
+ # Returns the value property for options[key]
162
+ def [](key)
163
+ self.options[key] ? self.options[key]["value"] : nil
164
+ end
165
+
166
+ # Public: Sets the value property for key in the cloudmaker options hash
167
+ #
168
+ # key - The key of the property you're accessing
169
+ # val - The value you wish to assign to the key
170
+ #
171
+ # Returns val
172
+ def []=(key, val)
173
+ if (self.options[key])
174
+ self.options[key]["value"] = val
175
+ else
176
+ self.options[key] = DEFAULT_KEY_PROPERTIES.merge('value' => val)
177
+ end
178
+ val
179
+ end
180
+
181
+ # Returns a String representation of the CloudMaker config
182
+ def inspect
183
+ "CloudMakerConfig#{self.options.inspect}"
184
+ end
185
+
186
+ class << self
187
+
188
+ # Public: Takes the path of a YAML file and loads a new Config object
189
+ # from it.
190
+ #
191
+ # instance_config_yaml - The path of the YAML file
192
+ #
193
+ # Returns a new Config
194
+ # Raises: Exception if the file doesn't exist.
195
+ # Raises: SyntaxError if the YAML file is invalid.
196
+ def from_yaml(instance_config_yaml)
197
+ begin
198
+ full_path = File.expand_path(instance_config_yaml)
199
+ cloud_yaml = File.open(full_path, "r") #Right_AWS will base64 encode this for us
200
+ rescue
201
+ raise "ERROR: The path to the CloudMaker config is incorrect"
202
+ end
203
+
204
+ CloudMaker::Config.new(YAML::load(cloud_yaml), :config_path => full_path)
205
+ end
206
+ end
207
+
208
+
209
+
210
+ private
211
+
212
+ # Internal: Takes a CloudMaker config and parses out the CloudMaker
213
+ # specific portions of it. For each key/value it fills in any property
214
+ # blanks from the DEFAULT_KEY_PROPERTIES. It also deletes the cloud_maker
215
+ # property from config.
216
+ #
217
+ # config - A hash that should contain a 'cloud_maker' key storing CloudMaker
218
+ # configuration properties.
219
+ #
220
+ # Returns a Hash in the format of
221
+ # {'key1' => {
222
+ # 'environment' => ...,
223
+ # 'value' => ...,
224
+ # 'required' ...
225
+ # }, 'key2' => ... , ...}
226
+ def extract_cloudmaker_config!(config)
227
+ cloud_maker_config = config.delete('cloud-maker') || {}
228
+ cloud_maker_config.keys.each do |key|
229
+ #if key is set to anything but a hash then we treat it as the value property
230
+ if !advanced_config?(cloud_maker_config[key])
231
+ cloud_maker_config[key] = {
232
+ "value" => cloud_maker_config[key]
233
+ }
234
+ end
235
+
236
+ cloud_maker_config[key] = DEFAULT_KEY_PROPERTIES.merge(cloud_maker_config[key])
237
+ end
238
+ cloud_maker_config
239
+ end
240
+
241
+ # Internal: Determines if value should be treated as a value or a property
242
+ # configuration hash. A property configuration hash would specify at least
243
+ # one of environment, value, or required.
244
+ #
245
+ # value - The value to evaluate
246
+ #
247
+ # Returns true if value is a property configuration, and false if it's just
248
+ # a value
249
+ def advanced_config?(value)
250
+ value.kind_of?(Hash) && !(DEFAULT_KEY_PROPERTIES.keys & value.keys).empty?
251
+ end
252
+
253
+ # Internal: Takes a CloudMaker config and parses out the includes list. If
254
+ # the list is an array it treats each entry as a URL. If it is a string it
255
+ # treats the string as the contents of a Cloud Init include file.
256
+ #
257
+ # config - A hash that should contain an 'include' key storing the include
258
+ # information.
259
+ #
260
+ # Returns an Array of URLs
261
+ def extract_includes!(config)
262
+ includes = config.delete('include')
263
+
264
+ #if we didn't specify it just use a blank array
265
+ if includes.nil?
266
+ includes = []
267
+ #if we passed something other than an array turn it into a string and split it up into urls
268
+ elsif !includes.kind_of?(Array)
269
+ includes = includes.to_s.split("\n")
270
+ includes.reject! {|line| line.strip[0] == "#" || line.strip.empty?}
271
+ end
272
+
273
+ includes
274
+ end
275
+
276
+ # Internal: Generates the shell command necessary to set an environment
277
+ # variable. It escapes the value but assumes there are no special characters
278
+ # in the key. If value is an array or a hash it generates an environment
279
+ # variable for each value in the array/hash with the key set to key_index.
280
+ #
281
+ # key - The key of the environment variable
282
+ # value - The value to set the key to
283
+ #
284
+ # Returns a string that can be executed to globally set the environment variable.
285
+ def set_environment_variable_cmd(key, value)
286
+ if (value.kind_of?(Hash))
287
+ value.keys.map { |hash_key|
288
+ set_environment_variable_cmd("#{key}_#{hash_key}", value[hash_key])
289
+ }.join(';')
290
+ elsif (value.kind_of?(Array))
291
+ strings = []
292
+ value.each_with_index { |arr_val, i|
293
+ strings.push(set_environment_variable_cmd("#{key}_#{i}", arr_val))
294
+ }
295
+ strings.join(';')
296
+ else
297
+ escaped_value = value.to_s.gsub(/"/, '\\\\\\\\\"')
298
+ "echo \"#{key}=\\\"#{escaped_value}\\\"\" >> /etc/environment"
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,80 @@
1
+ require 'right_aws'
2
+
3
+ module CloudMaker
4
+ class Ec2
5
+ # Public: Gets/Sets the AWS access key.
6
+ attr_accessor :aws_secret_access_key
7
+ # Public: Gets/Sets the AWS secret.
8
+ attr_accessor :aws_access_key_id
9
+ # Public: Gets/Sets the CloudMaker::Config
10
+ attr_accessor :config
11
+
12
+ # Public: Creates a new Ec2 instance
13
+ #
14
+ # cloud_maker_config - A CloudMaker::Config object describing the instance
15
+ # to be managed.
16
+ # options - S3 configuration options
17
+ # :aws_access_key_id - (required) The AWS access key
18
+ # :aws_secret_access_key - (required) The AWS secret
19
+ #
20
+ # Returns a new CloudMaker::Ec2 instance
21
+ # Raises RuntimeError if any of the required options are not specified
22
+ def initialize(cloud_maker_config, options)
23
+ required_keys = [:aws_access_key_id, :aws_secret_access_key]
24
+ unless (required_keys - options.keys).empty?
25
+ raise RuntimeError.new("Instantiated #{self.class} without required attributes: #{required_keys - options.keys}.")
26
+ end
27
+
28
+ self.config = cloud_maker_config
29
+ self.aws_access_key_id = options[:aws_access_key_id]
30
+ self.aws_secret_access_key = options[:aws_secret_access_key]
31
+ end
32
+
33
+ # Public: Launches a new EC2 instance, associates any specified elastic IPS
34
+ # with it, adds any specified tags, and archives the launch details to S3.
35
+ #
36
+ # Returns a RightAws supplied Hash describing the launched instance.
37
+ def launch
38
+ ec2 = RightAws::Ec2.new(self.aws_access_key_id, self.aws_secret_access_key)
39
+
40
+ user_data = self.config.to_user_data
41
+
42
+ instance = ec2.launch_instances(config['ami'],
43
+ :group_names => config['security_group'],
44
+ :instance_type => config['instance_type'],
45
+ :key_name => config['key_pair'],
46
+ :user_data => user_data
47
+ ).first
48
+
49
+ instance_id = instance[:aws_instance_id]
50
+
51
+ puts "Launched instance: #{instance_id.to_s.on_light_blue}"
52
+
53
+ ec2.create_tags(instance_id, self.config["tags"]) if self.config["tags"]
54
+
55
+ if (self.config["elastic_ip"])
56
+ #we can't associate IPs while the state is pending
57
+ while instance[:aws_state] == 'pending'
58
+ print '.'
59
+ STDOUT.flush
60
+ #this is going to hammer EC2 a bit, it might be necessary to add some delay in here
61
+ instance = ec2.describe_instances([instance_id]).first
62
+ end
63
+
64
+ ec2.associate_address(instance_id, :public_ip => self.config["elastic_ip"])
65
+
66
+ instance = ec2.describe_instances([instance_id]).first # So we get the correct IP address
67
+ end
68
+
69
+ archiver = S3Archiver.new(
70
+ :instance_id => instance_id,
71
+ :aws_access_key_id => self.aws_access_key_id,
72
+ :aws_secret_access_key => self.aws_secret_access_key,
73
+ :bucket_name => self.config["s3_archive_bucket"]
74
+ )
75
+ archiver.store_archive(self.config, instance)
76
+
77
+ instance
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,72 @@
1
+ require 'right_aws'
2
+
3
+ module CloudMaker
4
+ class S3Archiver
5
+
6
+ # Public: Gets/Sets the AWS access key.
7
+ attr_accessor :aws_secret_access_key
8
+ # Public: Gets/Sets the AWS secret.
9
+ attr_accessor :aws_access_key_id
10
+ # Public: Gets/Sets the EC2 instance ID string.
11
+ attr_accessor :instance_id
12
+ # Internal: Gets/Sets the RightAws::S3::Bucket used for storing/loading archives.
13
+ attr_accessor :bucket
14
+
15
+ # Public: All archive keys will be prefixed with KEY_PREFIX/
16
+ KEY_PREFIX = "cloud-maker"
17
+
18
+ # Public: Creates a new S3 Archiver instance
19
+ #
20
+ # options - S3 configuration options
21
+ # :aws_access_key_id - (required) The AWS access key
22
+ # :aws_secret_access_key - (required) The AWS secret
23
+ # :bucket_name - (required) The bucket for the archiver to access
24
+ # :instance_id - (required) The AWS instance ID the archive describes
25
+ #
26
+ # Returns a new CloudMaker::S3Archiver instance
27
+ # Raises RuntimeError if any of the required options are not specified
28
+ def initialize(options)
29
+ required_keys = [:aws_access_key_id, :aws_secret_access_key, :instance_id, :bucket_name]
30
+ unless (required_keys - options.keys).empty?
31
+ raise RuntimeError.new("Instantiated #{self.class} without required attributes: #{required_keys - options.keys}.")
32
+ end
33
+
34
+ self.instance_id = options[:instance_id]
35
+ self.aws_access_key_id = options[:aws_access_key_id]
36
+ self.aws_secret_access_key = options[:aws_secret_access_key]
37
+
38
+ s3 = RightAws::S3.new(self.aws_access_key_id, self.aws_secret_access_key)
39
+ self.bucket = s3.bucket(options[:bucket_name])
40
+ end
41
+
42
+ # Public: Generates an archive with all information relevant to an instance
43
+ # launch and stores it to S3.
44
+ #
45
+ # cloud_maker_config - The CloudMaker::Config the instance was launched with
46
+ # instance - A Hash describing the properties of the launched instance
47
+ #
48
+ # Returns nothing.
49
+ def store_archive(cloud_maker_config, instance)
50
+ userdata = cloud_maker_config.to_user_data
51
+ self.bucket.put(self.key + "/user_data.cloud_config", userdata)
52
+ self.bucket.put(self.key + "/instance.yaml", instance.to_yaml)
53
+ true
54
+ end
55
+
56
+ # Public: Retrieves a previously created archive from S3
57
+ #
58
+ # Returns the content of the archive.
59
+ def load_archive
60
+ self.bucket.get(self.key)
61
+ end
62
+
63
+ # Public: Returns the key that the archive will be stored under
64
+ def key
65
+ if self.instance_id
66
+ [KEY_PREFIX, self.instance_id].join('/')
67
+ else
68
+ raise RuntimeError.new("Attempted to generate a key name without an instance id.")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,38 @@
1
+ module CloudMaker
2
+ class ShellExecutor
3
+ YAML_DOMAIN = "!airbnb.com,2012-07-19"
4
+ YAML_TYPE = "shell-script"
5
+
6
+ attr_accessor :script
7
+
8
+ def initialize(script)
9
+ self.script = script
10
+ end
11
+
12
+ def to_yaml_type
13
+ "#{YAML_DOMAIN}/#{YAML_TYPE}"
14
+ end
15
+
16
+ def to_yaml(opts = {})
17
+ YAML.quick_emit( nil, opts ) { |out|
18
+ out.scalar( "tag:yaml.org,2002:str", execute, :plain )
19
+ }
20
+ end
21
+
22
+ def execute
23
+ @result ||= `#{self.script}`.chomp
24
+ end
25
+
26
+ def to_s
27
+ execute
28
+ end
29
+
30
+ def self.from_string_representation(string_representation)
31
+ ShellExecutor.new(string_representation)
32
+ end
33
+ end
34
+ end
35
+
36
+ YAML::add_domain_type(CloudMaker::ShellExecutor::YAML_DOMAIN, CloudMaker::ShellExecutor::YAML_TYPE) do |type, val|
37
+ CloudMaker::ShellExecutor.from_string_representation(val)
38
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloud-maker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Baxter
9
+ - Flo Leibert
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-07-20 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: colorize
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: thor
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '0.15'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '0.15'
47
+ - !ruby/object:Gem::Dependency
48
+ name: right_aws
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ description:
64
+ email: nathan.baxter@airbnb.cloud-maker
65
+ executables:
66
+ - cloud-maker
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - lib/cloud-maker.rb
71
+ - lib/cloud_maker/config.rb
72
+ - lib/cloud_maker/ec2.rb
73
+ - lib/cloud_maker/s3_archiver.rb
74
+ - lib/cloud_maker/shell_executor.rb
75
+ - bin/cloud-maker
76
+ homepage: https://github.com/airbnb/cloud-maker
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.24
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Launch and perform initial configuration of cloud servers.
100
+ test_files: []