aws-ami 0.0.2
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/README +1 -0
- data/bin/aws-ami +69 -0
- data/lib/aws_ami.rb +1 -0
- data/lib/aws_ami/ami.rb +92 -0
- data/lib/aws_ami/formation.json +63 -0
- metadata +68 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
= AWS AMI Tools
|
data/bin/aws-ami
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'aws_ami')
|
5
|
+
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: aws-ami -n [name] -r [region]"
|
12
|
+
|
13
|
+
opts.on("-n", "--name NAME", "New AMI name, include version number to version your AMIs") do |v|
|
14
|
+
options[:name] = v
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("-r", "--region REGION", "AWS Region that new AMI should be in") do |v|
|
18
|
+
options[:region] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-f", "--install_script_file INSTALL_SCRIPT_FILE", "The install script file, installs all packages and setup AMI") do |f|
|
22
|
+
options[:install_script] = f
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-k", "--ec2_ssh_key_name KEY_NAME", "The key name for accessing the ec2 instance that created for creating the AMI") do |n|
|
26
|
+
options[:key_name] = n
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-b", "--base_ami_yml BASE_AMI_YML", "A yaml file contains base ami for the new ami, region name and ami id key pairs") do |f|
|
30
|
+
options[:base_ami] = f
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-y", "--assume_yes", "Assume yes when asking for delete stack if the stack created failed, default is false") do
|
34
|
+
options[:assume_yes] = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-p", "--publish_to_account [AWS_ACCOUNT_NUMBER]", "AWS Account number; Allow another AWS Account to access the AMI created") do |v|
|
38
|
+
options[:publish_to_account] = v
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-h', '--help') do
|
42
|
+
puts opts
|
43
|
+
exit(0)
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('--dry', "output all options values") do
|
47
|
+
options[:dry] = true
|
48
|
+
end
|
49
|
+
end.parse!
|
50
|
+
|
51
|
+
if options[:dry]
|
52
|
+
require 'pp'
|
53
|
+
pp options
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
|
57
|
+
[:name, :region, :install_script, :key_name, :base_ami].each do |n|
|
58
|
+
raise "Must provide #{n}" unless options[n]
|
59
|
+
end
|
60
|
+
|
61
|
+
# ENV variables:
|
62
|
+
# AWS_ACCESS_KEY_ID
|
63
|
+
# AWS_SECRET_ACCESS_KEY
|
64
|
+
|
65
|
+
ami = AWS::AMI.new(options)
|
66
|
+
ami.build(options[:name],
|
67
|
+
'KeyName' => options[:key_name],
|
68
|
+
'InstallScript' => File.read(options[:install_script]),
|
69
|
+
"BaseAMI" => YAML.load(File.read(options[:base_ami]))[options[:region]])
|
data/lib/aws_ami.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'aws_ami/ami'
|
data/lib/aws_ami/ami.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module AWS
|
5
|
+
class AMI
|
6
|
+
# region: aws region of the new AMI
|
7
|
+
# assume_yes: true for deleting stack when creating image failed
|
8
|
+
def initialize(options={})
|
9
|
+
@assume_yes = options[:assume_yes]
|
10
|
+
@region = options[:region]
|
11
|
+
@publish_to_account = options[:publish_to_account]
|
12
|
+
end
|
13
|
+
|
14
|
+
# name: new AMI name, for example: mingle-saas-base
|
15
|
+
# parameters:
|
16
|
+
# BaseAMI: the base AMI id for the new AMI, for example:
|
17
|
+
# "ami-0d153248" for the "ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20121001" in us-west-1 region
|
18
|
+
# KeyName: the ssh key name for accessing the ec2 instance while building the AMI, this is only used when you need to debug problems
|
19
|
+
# InstallScript: the script installs everything need for the AMI, from 2048 to 16k bytes depending on the base ami provided
|
20
|
+
def build(name, parameters)
|
21
|
+
stack = cloudformation.stacks.create("build-#{name}-ami",
|
22
|
+
load_formation,
|
23
|
+
:disable_rollback => true,
|
24
|
+
:parameters => parameters)
|
25
|
+
logger.info "creating stack"
|
26
|
+
wait_until_created(stack)
|
27
|
+
begin
|
28
|
+
instance_id = stack.resources['EC2Instance'].physical_resource_id
|
29
|
+
|
30
|
+
logger.info "creating image"
|
31
|
+
image = ec2.instances[instance_id].create_image(name, :description => "Created at #{Time.now}")
|
32
|
+
sleep 2 until image.exists?
|
33
|
+
logger.info "image #{image.id} state: #{image.state}"
|
34
|
+
sleep 5 until image.state != :pending
|
35
|
+
if image.state == :failed
|
36
|
+
raise "Create image failed"
|
37
|
+
end
|
38
|
+
|
39
|
+
logger.info "image created"
|
40
|
+
logger.info "delete #{stack.name} stack"
|
41
|
+
stack.delete
|
42
|
+
rescue => e
|
43
|
+
logger.error "Creating AMI failed #{e.message}"
|
44
|
+
logger.error e.backtrace.join("\n")
|
45
|
+
logger.info "delete #{stack.name}? [y/n]"
|
46
|
+
if @assume_yes || gets.strip.downcase == 'y'
|
47
|
+
logger.info 'delete stack'
|
48
|
+
stack.delete
|
49
|
+
else
|
50
|
+
logger.info "left stack live"
|
51
|
+
end
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
if @publish_to_account
|
55
|
+
logger.info "add permissions for #{@publish_to_account}"
|
56
|
+
image.permissions.add(@publish_to_account.gsub(/-/, ''))
|
57
|
+
end
|
58
|
+
logger.info "Image #{name}[#{image.id}] created"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def ec2
|
63
|
+
@ec2 ||= AWS::EC2.new(:ec2_endpoint => "ec2.#{@region}.amazonaws.com")
|
64
|
+
end
|
65
|
+
|
66
|
+
def cloudformation
|
67
|
+
@cfm ||= AWS::CloudFormation.new(:cloud_formation_endpoint => "cloudformation.#{@region}.amazonaws.com")
|
68
|
+
end
|
69
|
+
|
70
|
+
def wait_until_created(stack)
|
71
|
+
loop do
|
72
|
+
case stack.status.to_s
|
73
|
+
when /^create_complete$/i
|
74
|
+
break
|
75
|
+
when /^create_(failed|rollback_complete)$/i
|
76
|
+
raise "Create Stack failed"
|
77
|
+
end
|
78
|
+
event = stack.events.first
|
79
|
+
logger.info("latest event: #{event.resource_type} #{event.resource_status}")
|
80
|
+
sleep 5
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def load_formation
|
85
|
+
File.read(File.join(File.dirname(__FILE__), 'formation.json'))
|
86
|
+
end
|
87
|
+
|
88
|
+
def logger
|
89
|
+
@logger ||= Logger.new(STDOUT)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
{
|
2
|
+
"Description": "Build AMI resource formation",
|
3
|
+
|
4
|
+
"Parameters": {
|
5
|
+
"KeyName": {
|
6
|
+
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
|
7
|
+
"Type": "String"
|
8
|
+
},
|
9
|
+
"InstallScript": {
|
10
|
+
"Description": "Script to install everything need for the AMI",
|
11
|
+
"Type": "String"
|
12
|
+
},
|
13
|
+
"BaseAMI": {
|
14
|
+
"Description": "Base AMI for the new AMI",
|
15
|
+
"Type": "String"
|
16
|
+
}
|
17
|
+
},
|
18
|
+
|
19
|
+
"Resources": {
|
20
|
+
"SecurityGroup" : {
|
21
|
+
"Type" : "AWS::EC2::SecurityGroup",
|
22
|
+
"Properties" : {
|
23
|
+
"GroupDescription" : "Enable SSH access via port 22",
|
24
|
+
"SecurityGroupIngress" : [ {
|
25
|
+
"IpProtocol" : "tcp",
|
26
|
+
"FromPort" : "22",
|
27
|
+
"ToPort" : "22",
|
28
|
+
"CidrIp" : "0.0.0.0/0"
|
29
|
+
} ]
|
30
|
+
}
|
31
|
+
},
|
32
|
+
|
33
|
+
"EC2Instance": {
|
34
|
+
"Type": "AWS::EC2::Instance",
|
35
|
+
"Properties": {
|
36
|
+
"ImageId": { "Ref": "BaseAMI" },
|
37
|
+
"InstanceType": "m1.small",
|
38
|
+
"KeyName": { "Ref": "KeyName" },
|
39
|
+
"SecurityGroups": [ { "Ref": "SecurityGroup" } ],
|
40
|
+
"UserData": { "Fn::Base64": { "Fn::Join": ["", [
|
41
|
+
{ "Ref": "InstallScript" },
|
42
|
+
"\ncurl -X PUT -H 'Content-Type:' --data-binary '{\"Status\" : \"SUCCESS\",",
|
43
|
+
" \"Reason\" : \"ec2 instance launched\",",
|
44
|
+
" \"UniqueId\" : \"", {"Ref": "AWS::StackName"}, "-ec2-success\",",
|
45
|
+
" \"Data\" : \"Done\"}' ",
|
46
|
+
" \"", {"Ref" : "myWaitHandle"},"\"\n"
|
47
|
+
]]}}
|
48
|
+
}
|
49
|
+
},
|
50
|
+
"myWaitHandle" : {
|
51
|
+
"Type" : "AWS::CloudFormation::WaitConditionHandle",
|
52
|
+
"Properties" : {
|
53
|
+
}
|
54
|
+
},
|
55
|
+
"myWaitCondition" : {
|
56
|
+
"Type" : "AWS::CloudFormation::WaitCondition",
|
57
|
+
"Properties" : {
|
58
|
+
"Handle" : { "Ref" : "myWaitHandle" },
|
59
|
+
"Timeout" : "300"
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws-ami
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mingle SaaS team
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.8.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.8.2
|
30
|
+
description:
|
31
|
+
email: mingle.saas@thoughtworks.com
|
32
|
+
executables:
|
33
|
+
- aws-ami
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- README
|
38
|
+
- lib/aws_ami/ami.rb
|
39
|
+
- lib/aws_ami.rb
|
40
|
+
- bin/aws-ami
|
41
|
+
- lib/aws_ami/formation.json
|
42
|
+
homepage: http://github.com/ThoughtWorksStudios/aws-ami
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
post_install_message: run aws-ami -h for details how to use
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.24
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: AWS AMI toolsets for Ruby
|
67
|
+
test_files: []
|
68
|
+
has_rdoc:
|