aws-ami 0.0.2

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