amazon-instance 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/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