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 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: