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 +90 -0
- data/lib/cloud-maker.rb +4 -0
- data/lib/cloud_maker/config.rb +302 -0
- data/lib/cloud_maker/ec2.rb +80 -0
- data/lib/cloud_maker/s3_archiver.rb +72 -0
- data/lib/cloud_maker/shell_executor.rb +38 -0
- metadata +100 -0
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
|
data/lib/cloud-maker.rb
ADDED
@@ -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: []
|