amazon-instance 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ New BSD Licence
2
+
3
+ Copyright (c) 2010, Alfonso Jimenez All rights reserved.
4
+
5
+ Authors: Alfonso Jimenez
6
+
7
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that
8
+ the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+ following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
13
+ the following disclaimer in the documentation and/or other materials provided with the distribution.
14
+ * Neither the name of the Xurrency nor the names of its contributors may be used to endorse or promote
15
+ products derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY ALFONSO JIMENEZ ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19
+ NO EVENT SHALL WEBLOGS SL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.textile ADDED
@@ -0,0 +1,84 @@
1
+ h1. amazon-instance
2
+
3
+ amazon-instance is a tool for creating new Amazon EC2 instances. It allows you to organize your instances
4
+ by projects and environments.
5
+
6
+ h2. Installation
7
+
8
+ <pre><code>gem install amazon-instance</code></pre>
9
+
10
+ h2. Configuration
11
+
12
+ The configuration directory has got the following structure:
13
+
14
+ <pre><code>
15
+ config/
16
+ general.yml
17
+ projects/
18
+ example.yml
19
+ example2.yml
20
+ ..
21
+ on-boot/
22
+ script.sh
23
+ script2.sh
24
+ ..
25
+ </code></pre>
26
+
27
+ @general.yml@
28
+
29
+ This file includes the basic configuration.
30
+
31
+ <pre><code>
32
+ access_key_id: YOUR_ACCESS_KEY
33
+ secret_key_id: YOUR_SECRET_KEY
34
+ ec2_zone_url: https://eu-west-1.ec2.amazonaws.com
35
+ </code></pre>
36
+
37
+ @projects/@
38
+
39
+ Each file defines a particular project. Instance options such as AMI id, type, ... should be provided here.
40
+ This files are organized by environments. For example, if we want to setup a project called "my-personal-site"
41
+ with two environments (development and production), we could do something like this:
42
+
43
+ @my-personal-site.yml@
44
+ <pre><code>development:
45
+ :key_name: mykeys
46
+ :availability_zone: eu-west-1a
47
+ :instance_type: m1.small
48
+ :security_group: my-project-development-web
49
+ :image_id: ami-52794c26
50
+ :on_boot: script.sh
51
+
52
+ production:
53
+ :key_name: mykeys
54
+ :availability_zone: eu-west-1a
55
+ :instance_type: m1.small
56
+ :security_group: my-project-production-web
57
+ :image_id: ami-52794c26
58
+ :on_boot: script.sh
59
+ </code></pre>
60
+
61
+ Notice that we are saying that we want to run script.sh once the instance is created. This script should be
62
+ placed into the on-boot directory.
63
+
64
+ h2. Usage
65
+
66
+ You can launch new Amazon EC2 instances by using the command @amazon-instance@
67
+
68
+ <pre><code>
69
+ alfonso@alexandra:~$ amazon-instance -h
70
+ Options:
71
+ --environment, -e <s>: Environment (Required)
72
+ --project, -p <s>: Project (Required)
73
+ --config-dir, -c <s>: Config directory (optional)
74
+ --quiet, -q: Quiet mode (optional)
75
+ --help, -h: Show this message
76
+ </code></pre>
77
+
78
+ For example:
79
+
80
+ <pre><code>alfonso@alexandra:~$ amazon-instance -p my-project -e development</pre></code>
81
+
82
+ h2. License
83
+
84
+ See LICENSE for details
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec'
4
+
5
+ gem 'rspec', '>= 2.0.0.beta.20'
6
+ gem 'rspec-expectations'
7
+
8
+ require 'rspec/core/rake_task'
9
+
10
+ desc 'Test invalid instance'
11
+ RSpec::Core::RakeTask.new do |t|
12
+ end
13
+
14
+ task :default => :spec
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'rubygems'
6
+ require 'trollop'
7
+ require 'amazon-instance'
8
+
9
+ class LaunchAmazon
10
+ def run
11
+ options = Trollop::options do
12
+ opt :environment, 'Environment', :short => '-e',:type => String, :required => true
13
+ opt :project, 'Project Name', :short => '-p', :type => String, :required => true
14
+ opt :config_dir, 'Config directory', :short => '-c', :type => String, :default => File.join(File.dirname(__FILE__), '../config')
15
+ opt :quiet, 'Quiet mode', :short => '-q', :default => true
16
+ end
17
+
18
+ begin
19
+ instance = AmazonInstance.new(options.environment,
20
+ options.project,
21
+ options.config_dir+'/',
22
+ options.quiet)
23
+ instance.launch
24
+ rescue Exception => e
25
+ puts "\e[31m[error] #{e.message}\e[0m"
26
+ end
27
+ end
28
+ end
29
+
30
+ # Run the application
31
+ app = LaunchAmazon.new
32
+ app.run
@@ -0,0 +1,3 @@
1
+ access_key_id: YOUR_ACCESS_KEY
2
+ secret_key_id: YOUR_SECRET_KEY
3
+ ec2_zone_url: https://eu-west-1.ec2.amazonaws.com
@@ -0,0 +1,3 @@
1
+ #!/bin/sh -x
2
+
3
+ #This script is being executed on the instance when booting first time
@@ -0,0 +1,31 @@
1
+ dev:
2
+ :key_name: mykeys
3
+ :availability_zone: eu-west-1a
4
+ :instance_type: m1.small
5
+ :security_group: example-dev-web
6
+ :image_id: ami-52794c26
7
+ :on_boot: script.sh
8
+
9
+ test:
10
+ :key_name: mykeys
11
+ :availability_zone: eu-west-1a
12
+ :instance_type: m1.small
13
+ :security_group: example-test-web
14
+ :image_id: ami-52794c26
15
+ :on_boot: script.sh
16
+
17
+ qa:
18
+ :key_name: mykeys
19
+ :availability_zone: eu-west-1a
20
+ :instance_type: m1.small
21
+ :security_group: example-qa-web
22
+ :image_id: ami-52794c26
23
+ :on_boot: script.sh
24
+
25
+ prod:
26
+ :key_name: mykeys
27
+ :availability_zone: eu-west-1a
28
+ :instance_type: c1.medium
29
+ :security_group: example-prod-web
30
+ :image_id: ami-52794c26
31
+ :on_boot: script.sh
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+
3
+ require File.dirname(__FILE__)+'/amazon-instance/project'
4
+ require File.dirname(__FILE__)+'/amazon-instance/amazon-ec2'
5
+
6
+ class AmazonInstance
7
+ attr_accessor :config
8
+ attr_accessor :config_dir
9
+ attr_accessor :environment
10
+ attr_accessor :project
11
+ attr_accessor :verbose
12
+
13
+ def initialize(environment, project, config_dir, verbose=false)
14
+ @config = load_general_config(config_dir)
15
+
16
+ @config_dir = config_dir
17
+ @environment = environment
18
+ @verbose = verbose
19
+
20
+ @project = Project::load(project, environment, config_dir)
21
+ end
22
+
23
+ def launch(ec2=nil)
24
+ ec2 = AmazonEC2.new(@config['access_key_id'],
25
+ @config['secret_key_id'],
26
+ URI.parse(@config['ec2_zone_url']).host) if ec2.nil?
27
+
28
+ group = @project[:security_group]
29
+
30
+ raise 'Invalid security group: '+group unless ec2.valid_group?(group)
31
+
32
+ print_message('Creating instance') if @verbose
33
+ host = ec2.launch_instance(@environment, @project, @config_dir)
34
+ print_message('Instance created', :ok) if @verbose
35
+
36
+ # Wait till ssh port is open
37
+ print_message('Checking SSH port') if @verbose
38
+ ec2.sleep_till_ssh_is_open(host)
39
+ print_message('SSH port open: '+host, :ok) if @verbose
40
+
41
+ host
42
+ end
43
+
44
+ private
45
+ def load_general_config(config_dir)
46
+ config = nil
47
+
48
+ File.open(config_dir+'general.yml') do |content|
49
+ config = YAML::load(content.read)
50
+ end
51
+
52
+ config
53
+ end
54
+
55
+ def print_message(message, level=:info)
56
+ colours = {:info => 33, :error => 31, :ok => 32}
57
+
58
+ puts "\e[#{colours[level]}m[#{level.to_s}] #{message}\e[0m"
59
+ end
60
+ end
@@ -0,0 +1,72 @@
1
+ require 'AWS'
2
+ require 'socket'
3
+ require 'timeout'
4
+
5
+ class AmazonEC2
6
+ def initialize(access_key, secret_key, server)
7
+ @ec2 = AWS::EC2::Base.new(:access_key_id => access_key,
8
+ :secret_access_key => secret_key,
9
+ :server => server)
10
+ end
11
+
12
+ def launch_instance(environment, config, config_dir)
13
+ config[:base64_encoded] = true
14
+
15
+ if !config[:on_boot].nil?
16
+ File.open(config_dir+'on-boot/'+config[:on_boot]) do |content|
17
+ config[:user_data] = content.read.gsub!('{environment}', environment)
18
+ end
19
+ end
20
+
21
+ instance = @ec2.run_instances(config.to_hash)
22
+
23
+ instanceId = instance['instancesSet']['item'].first['instanceId']
24
+
25
+ host_by_instance(instanceId)
26
+ end
27
+
28
+ def valid_group?(group_name)
29
+ valid_group = false
30
+
31
+ @ec2.describe_security_groups['securityGroupInfo']['item'].each do |group|
32
+ valid_group = true if group['groupName'] == group_name
33
+ end
34
+
35
+ valid_group
36
+ end
37
+
38
+ def host_by_instance(instance_id)
39
+ host = nil
40
+
41
+ while host == nil do
42
+ instances = @ec2.describe_instances({:instance_id => instance_id})
43
+ host = instances['reservationSet']['item'].first['instancesSet']['item'].first['dnsName']
44
+ sleep 2
45
+ end
46
+
47
+ host
48
+ end
49
+
50
+ def sleep_till_ssh_is_open(host)
51
+ while ssh_open?(host) do
52
+ sleep(3)
53
+ end
54
+ end
55
+
56
+ def ssh_open?(host)
57
+ begin
58
+ Timeout::timeout(1) do
59
+ begin
60
+ s = TCPSocket.new(host, 22)
61
+ s.close
62
+ return true
63
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
64
+ return false
65
+ end
66
+ end
67
+ rescue Timeout::Error
68
+ end
69
+
70
+ return false
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ class Project
2
+ def self.load(name, environment, config_dir)
3
+ config = nil
4
+ filename = config_dir+'projects/'+name+'.yml'
5
+
6
+ if File.exist?(filename)
7
+ File.open(filename) do |content|
8
+ config_environments = YAML::load(content)
9
+
10
+ if !config_environments[environment].nil?
11
+ config = config_environments[environment]
12
+ else
13
+ raise 'Invalid environment name: '+environment
14
+ end
15
+ end
16
+ else
17
+ raise 'File: '+name+'.yml does not exist'
18
+ end
19
+
20
+ config
21
+ end
22
+
23
+ def self.list_available_projects(config_dir)
24
+ Dir.foreach(pathname) do |file|
25
+ puts file
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'amazon-instance'
6
+
7
+ describe 'AmazonInstance' do
8
+ before :all do
9
+ @config_dir = File.join(File.dirname(__FILE__), 'fixtures/')
10
+ @project_name = 'example'
11
+ end
12
+
13
+ context 'When an instance is created' do
14
+ it 'with a valid environment name' do
15
+ instance = AmazonInstance.new('test', @project_name, @config_dir)
16
+ instance.environment.should == 'test'
17
+
18
+ instance = AmazonInstance.new('qa', @project_name, @config_dir)
19
+ instance.environment.should == 'qa'
20
+
21
+ instance = AmazonInstance.new('prod', @project_name, @config_dir)
22
+ instance.environment.should == 'prod'
23
+
24
+ instance = AmazonInstance.new('dev', @project_name, @config_dir)
25
+ instance.environment.should == 'dev'
26
+ end
27
+
28
+ it 'with an invalid environment name should raise an exception' do
29
+ expect {AmazonInstance.new('xxx', @project_name, @config_dir)}.to raise_error('Invalid environment name: xxx')
30
+ expect {AmazonInstance.new('yyy',@project_name, @config_dir)}.to raise_error('Invalid environment name: yyy')
31
+ end
32
+
33
+ it 'with an invalid project name should raise an exception' do
34
+ expect {AmazonInstance.new('dev', 'invalid_config', @config_dir)}.to raise_error('File: invalid_config.yml does not exist')
35
+ end
36
+ end
37
+
38
+ context 'When launching an instance' do
39
+ it 'should return the instance host' do
40
+ ec2 = mock('AmazonEC2')
41
+ ec2.should_receive(:valid_group?).with(any_args()).and_return(true)
42
+ ec2.should_receive(:launch_instance).with(any_args()).and_return('amazon-host.com')
43
+ ec2.should_receive(:sleep_till_ssh_is_open).with(any_args()).and_return(true)
44
+
45
+ instance = AmazonInstance.new('test', @project_name, @config_dir)
46
+ instance.launch(ec2).should == 'amazon-host.com'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ access_key_id: YOUR_ACCESS_KEY
2
+ secret_key_id: YOUR_SECRET_KEY
3
+ ec2_zone_url: https://eu-west-1.ec2.amazonaws.com
@@ -0,0 +1,3 @@
1
+ #!/bin/sh -x
2
+
3
+ #This script is being executed on the instance when booting first time
@@ -0,0 +1,31 @@
1
+ dev:
2
+ :key_name: mykeys
3
+ :availability_zone: eu-west-1a
4
+ :instance_type: m1.small
5
+ :security_group: example-dev-web
6
+ :image_id: ami-52794c26
7
+ :on_boot: script.sh
8
+
9
+ test:
10
+ :key_name: mykeys
11
+ :availability_zone: eu-west-1a
12
+ :instance_type: m1.small
13
+ :security_group: example-test-web
14
+ :image_id: ami-52794c26
15
+ :on_boot: script.sh
16
+
17
+ qa:
18
+ :key_name: mykeys
19
+ :availability_zone: eu-west-1a
20
+ :instance_type: m1.small
21
+ :security_group: example-qa-web
22
+ :image_id: ami-52794c26
23
+ :on_boot: script.sh
24
+
25
+ prod:
26
+ :key_name: mykeys
27
+ :availability_zone: eu-west-1a
28
+ :instance_type: c1.medium
29
+ :security_group: example-prod-web
30
+ :image_id: ami-52794c26
31
+ :on_boot: script.sh
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amazon-instance
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Alfonso Jimenez
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-16 00:00:00 +02:00
19
+ default_executable: amazon-instance
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: amazon-ec2
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 37
30
+ segments:
31
+ - 0
32
+ - 9
33
+ - 15
34
+ version: 0.9.15
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 62196427
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 0
50
+ - beta
51
+ - 20
52
+ version: 2.0.0.beta.20
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: trollop
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 83
64
+ segments:
65
+ - 1
66
+ - 16
67
+ - 2
68
+ version: 1.16.2
69
+ type: :runtime
70
+ version_requirements: *id003
71
+ description: A tool for creating new Amazon EC2 instances. It allows you to organize your instances by projects and environments.
72
+ email: yo@alfonsojimenez.com
73
+ executables:
74
+ - amazon-instance
75
+ extensions: []
76
+
77
+ extra_rdoc_files:
78
+ - LICENSE
79
+ files:
80
+ - LICENSE
81
+ - README.textile
82
+ - Rakefile
83
+ - lib/amazon-instance/amazon-ec2.rb
84
+ - lib/amazon-instance/project.rb
85
+ - lib/amazon-instance.rb
86
+ - bin/amazon-instance
87
+ - spec/amazon_instance_spec.rb
88
+ - spec/fixtures/general.yml
89
+ - spec/fixtures/projects/example.yml
90
+ - spec/fixtures/on-boot/script.sh
91
+ - config/general.yml
92
+ - config/projects/example.yml
93
+ - config/on-boot/script.sh
94
+ has_rdoc: true
95
+ homepage: http://github.com/alfonsojimenez/amazon-instance
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options:
100
+ - --charset=UTF-8
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.3.7
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: A tool for creating new Amazon EC2 instances
128
+ test_files:
129
+ - spec/amazon_instance_spec.rb