cloud-maker 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []