stemcell 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .stemcellrc
19
+ \#*\#
20
+ .\#*
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Stemcell
2
2
 
3
- #: Write a gem description
3
+ Stemcell launches instances
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,7 +18,6 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- #: Write usage instructions here
22
21
 
23
22
  ## Contributing
24
23
 
data/bin/stem ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # -*- mode: shell -*-
4
+
5
+ require_relative '../lib/stemcell'
6
+
7
+ require 'trollop'
8
+
9
+ options = Trollop::options do
10
+ version "stemcell #{Stemcell::VERSION} (c) 2013 Martin Rhoads"
11
+ banner <<END_OF_BANNER
12
+ welcome to stemcell
13
+ END_OF_BANNER
14
+
15
+ opt('aws_access_key',
16
+ "aws access key",
17
+ :type => String,
18
+ :default => ENV['AWS_ACCESS_KEY']
19
+ )
20
+
21
+ opt('aws_secret_key',
22
+ "aws secret key",
23
+ :type => String,
24
+ :default => ENV['AWS_SECRET_KEY']
25
+ )
26
+
27
+ opt('chef_validation_key_name',
28
+ 'chef validation key name',
29
+ :type => String,
30
+ :default => ENV['CHEF_VALIDATION_KEY_NAME'],
31
+ )
32
+
33
+ opt('chef_validation_key',
34
+ 'chef validation key path',
35
+ :type => String,
36
+ :default => ENV['CHEF_VALIDATION_KEY'],
37
+ )
38
+
39
+ opt('chef_data_bag_secret',
40
+ 'path to chef data bag encryption secret',
41
+ :type => String,
42
+ :default => ENV['CHEF_DATA_BAG_SECRET'],
43
+ )
44
+
45
+ opt('chef_role',
46
+ 'chef role of instance to be launched',
47
+ :type => String,
48
+ :default => ENV['CHEF_ROLE'],
49
+ )
50
+
51
+ opt('chef_environment',
52
+ 'chef environment of instance to be launched',
53
+ :type => String,
54
+ :default => ENV['CHEF_ENVIRONMENT'],
55
+ )
56
+
57
+ opt('key_name',
58
+ 'ssh key to launch instance with',
59
+ :type => String,
60
+ :default => ENV['KEY_NAME'],
61
+ )
62
+
63
+ opt('count',
64
+ 'number of instances to launch',
65
+ :type => String,
66
+ :default => ENV['COUNT'],
67
+ )
68
+
69
+ opt('region',
70
+ 'ec2 region to launch in',
71
+ :type => String,
72
+ :default => ENV['REGION'],
73
+ )
74
+
75
+ opt('image',
76
+ 'ami to use for launch',
77
+ :type => String,
78
+ :default => ENV['AMI'],
79
+ )
80
+
81
+ opt('security_group',
82
+ 'security group to launch instance with',
83
+ :type => String,
84
+ :default => ENV['SECURITY_GROUP'],
85
+ )
86
+
87
+ opt('machine_type',
88
+ 'instance type to launch',
89
+ :type => String,
90
+ :default => ENV['MACHINE_TYPE'],
91
+ )
92
+
93
+ end
94
+
95
+ required_parameters = [
96
+ 'aws_access_key',
97
+ 'aws_secret_key',
98
+ 'chef_validation_key_name',
99
+ 'chef_validation_key',
100
+ 'chef_role',
101
+ 'chef_environment',
102
+ 'key_name',
103
+ ]
104
+
105
+ required_parameters.each do |arg|
106
+ raise ArgumentError, "--#{arg} needs to be specified on the commandline or set \
107
+ by the #{arg.upcase.gsub('-','_')} environment variable" if
108
+ options[arg].nil? or ! options[arg]
109
+ end
110
+
111
+ stemcell = Stemcell::Stemcell.new(options)
112
+ stemcell.launch({'count' => options['count']})
113
+
@@ -0,0 +1,130 @@
1
+ #!/bin/bash -ex
2
+ #
3
+ # This script will bootstrap and run chef
4
+ #
5
+ # You need to specify a run_list, environment, and validator key info below
6
+ #
7
+ # Martin Rhoads
8
+
9
+
10
+ set -o pipefail
11
+
12
+
13
+ # redirect stdout to /var/log/init
14
+ exec > /var/log/init
15
+
16
+ # redirect stderr to /var/log/init.err
17
+ exec 2> /var/log/init.err
18
+
19
+
20
+ ##
21
+ ## settings
22
+ ##
23
+
24
+
25
+ run_list=role[<%= @chef_role %>]
26
+ environment=<%= @chef_environment %>
27
+ validator_name=<%= @chef_validation_key_name %>
28
+ validator_value="<%= @chef_validation_key_value %>"
29
+ data_bag_secret="<%= @chef_data_bag_secret %>"
30
+
31
+ ##
32
+ ## common function
33
+ ##
34
+
35
+
36
+ update() {
37
+ echo updating apt repo
38
+ apt-get update 1>&2
39
+ }
40
+
41
+
42
+ install() {
43
+ if ! (dpkg -l | awk '{print $2}' | grep -q ^$1$ ) ; then
44
+ echo installing $1...
45
+ export DEBIAN_FRONTEND=noninteractive
46
+ apt-get install -y $1 1>&2
47
+ fi
48
+ }
49
+
50
+
51
+ get_instance_id() {
52
+ instance_id=`curl --retry 5 --retry-delay 5 169.254.169.254/latest/meta-data/instance-id` 1>&2
53
+ echo instance_id is $instance_id
54
+ }
55
+
56
+
57
+ configure_chef() {
58
+ echo configuring chef...
59
+ mkdir -p /etc/chef
60
+ echo -e "$validator_value" > /etc/chef/validation.pem
61
+ cat<<EOF>/etc/chef/client.rb
62
+ log_level :info
63
+ log_location STDOUT
64
+ chef_server_url "https://api.opscode.com/organizations/airbnb"
65
+ validation_client_name "$validator_name"
66
+ node_name "$instance_id"
67
+ EOF
68
+ echo -e "$data_bag_secret" > /etc/chef/encrypted_data_bag_secret
69
+ echo chef configured
70
+ }
71
+
72
+
73
+ install_chef() {
74
+ if ! which chef-client > /dev/null ; then
75
+ echo installing chef via omnibus...
76
+ curl -L --silent https://www.opscode.com/chef/install.sh | sudo bash 1>&2
77
+ curl -L --silent https://www.opscode.com/chef/install.sh | sudo bash -s -- -v 11.2.0 1>&2
78
+ else
79
+ echo chef is already installed
80
+ fi
81
+ }
82
+
83
+
84
+ configure_chef_daemon() {
85
+ cat<<EOF>/etc/init/chef-client.conf
86
+ description "chef-client"
87
+ author "Martin Rhoads"
88
+ start on networking
89
+ script
90
+ chef-client --interval 300 --splay 150 | logger -t chef-client 2>&1
91
+ end script
92
+ respawn
93
+ EOF
94
+ }
95
+
96
+
97
+ run_chef() {
98
+ echo running chef-client...
99
+ chef-client --environment $environment --override-runlist $run_list 1>&2
100
+ echo done running chef-client
101
+ }
102
+
103
+
104
+ start_chef_daemon() {
105
+ start chef-client
106
+ }
107
+
108
+
109
+ ##
110
+ ## main
111
+ ##
112
+
113
+
114
+ echo starting chef bootstrapping...
115
+ update
116
+ install curl
117
+ get_instance_id
118
+ configure_chef
119
+ install_chef
120
+ run_chef
121
+ configure_chef_daemon
122
+ start_chef_daemon
123
+
124
+
125
+ ##
126
+ ## exit
127
+ ##
128
+
129
+
130
+ echo "ran successfully:)"
@@ -1,3 +1,3 @@
1
1
  module Stemcell
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/stemcell.rb CHANGED
@@ -1,5 +1,136 @@
1
- require "stemcell/version"
1
+ require 'logger'
2
+ require 'erb'
3
+ require 'aws-sdk'
4
+
5
+ require_relative "stemcell/version"
2
6
 
3
7
  module Stemcell
4
- # Your code goes here...
8
+ class Stemcell
9
+ def initialize(opts={})
10
+ @log = Logger.new(STDOUT)
11
+ @log.debug "opts are #{opts.inspect}"
12
+ ['aws_access_key',
13
+ 'aws_secret_key',
14
+ 'chef_validation_key_name',
15
+ 'chef_validation_key',
16
+ 'chef_role',
17
+ 'chef_environment',
18
+ 'key_name',
19
+ ].each do |req|
20
+ raise ArgumentError, "missing required param #{req}" unless opts[req]
21
+ instance_variable_set("@#{req}",opts[req])
22
+ end
23
+
24
+ @security_group = opts['security_group'] ? opts['security_group'] : 'default'
25
+ @image = opts['image'] ? opts['image'] : 'ami-d726abbe'
26
+ @machine_type = opts['machine_type'] ? opts['machine_type'] : 'm1.small'
27
+ @region = opts['region'] ? opts['region'] : 'us-east-1'
28
+ @ec2_url = "ec2.#{@region}.amazonaws.com"
29
+ @timeout = 120
30
+ @start_time = Time.new
31
+
32
+ begin
33
+ @chef_validation_key_value = File.read(@chef_validation_key)
34
+ rescue Object => e
35
+ # TODO(mkr): we may want to do something better here
36
+ @chef_validation_key_value = @chef_validation_key
37
+ # raise "\ncould not open specified key #{@chef_validation_key}:\n#{e.inspect}#{e.backtrace}"
38
+ end
39
+
40
+ if opts['chef_data_bag_secret']
41
+ begin
42
+ @chef_data_bag_secret = File.read(opts['chef_data_bag_secret'])
43
+ rescue Object => e
44
+ raise "\ncould not open specified secret key file #{opts['chef_data_bag_secret']}:\n#{e.inspect}#{e.backtrace}"
45
+ end
46
+ else
47
+ @chef_data_bag_secret = ''
48
+ end
49
+
50
+ AWS.config({:access_key_id => @aws_access_key, :secret_access_key => @aws_secret_key})
51
+ @ec2 = AWS::EC2.new(:ec2_endpoint => @ec2_url)
52
+ @ec2_region = @ec2.regions[@region]
53
+
54
+ @user_data = render_template
55
+ end
56
+
57
+ def launch(opts={})
58
+ File.open('/tmp/user-data', 'w') {|f| f.write(@user_data) }
59
+ instances = do_launch(opts)
60
+ wait(instances)
61
+ print_run_info(instances)
62
+ print_config_info
63
+ return instances
64
+ end
65
+
66
+ private
67
+
68
+ def print_config_info
69
+ puts "install logs will be in /var/log/init and /var/log/init.err"
70
+ end
71
+
72
+ def print_run_info(instances)
73
+ puts "here is the info for what's launched:"
74
+ instances.each do |instance|
75
+ puts "\tinstance_id: #{instance.instance_id}"
76
+ puts "\tpublic ip: #{instance.public_ip_address}"
77
+ puts
78
+ end
79
+ end
80
+
81
+ def wait(instances)
82
+ sleep 3
83
+ while true
84
+ if Time.now - @start_time > @timeout
85
+ bail(instances)
86
+ raise TimeoutError, "exceded timeout of #{@timeout}"
87
+ end
88
+ puts "instances is #{instances.inspect}"
89
+ if instances.select{|i| i.status != :running }.empty?
90
+ @log.info "all instances in running state"
91
+ return
92
+ end
93
+ @log.info "instances not ready yet. sleeping..."
94
+ sleep 5
95
+ return wait(instances)
96
+ end
97
+ end
98
+
99
+ def do_launch(opts={})
100
+ options = {
101
+ :image_id => @image,
102
+ :security_groups => @security_group,
103
+ :user_data => @user_data,
104
+ :instance_type => @machine_type,
105
+ :key_name => @key_name,
106
+ }
107
+ options.merge!({:availability_zone => opts['avilibility_zone']}) if opts['availability_zone']
108
+ options.merge!({:count => opts['count']}) if opts['count']
109
+ puts "creating instance with options:\n#{options}"
110
+ instances = @ec2_region.instances.create(options)
111
+ instances = [instances] unless instances.class == Array
112
+ instances.each do |instance|
113
+ @log.info "launched instance #{instance.instance_id}"
114
+ end
115
+ return instances
116
+ end
117
+
118
+ def render_template
119
+ this_file = File.expand_path __FILE__
120
+ base_dir = File.dirname this_file
121
+ template_file_path = File.join(base_dir,'stemcell','templates','bootstrap.sh.erb')
122
+ template_file = File.read(template_file_path)
123
+ erb_template = ERB.new(template_file)
124
+ generated_template = erb_template.result(binding)
125
+ return generated_template
126
+ end
127
+
128
+ def bail(instances)
129
+ return if instances.nil?
130
+ instances.each do |instance|
131
+ instance.delete
132
+ end
133
+ end
134
+
135
+ end
5
136
  end
data/stemcell.gemspec CHANGED
@@ -8,12 +8,15 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Stemcell::VERSION
9
9
  gem.authors = ["Martin Rhoads"]
10
10
  gem.email = ["martin.rhoads@airbnb.com"]
11
- gem.description = %q{Write a gem description}
12
- gem.summary = %q{Write a gem summary}
11
+ gem.description = %q{stemcell launches instances}
12
+ gem.summary = %q{no summary}
13
13
  gem.homepage = ""
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+ gem.add_runtime_dependency 'trollop'
20
+ gem.add_runtime_dependency 'aws-sdk'
19
21
  end
22
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stemcell
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,45 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-20 00:00:00.000000000 Z
13
- dependencies: []
14
- description: Write a gem description
12
+ date: 2013-02-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: trollop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aws-sdk
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: stemcell launches instances
15
47
  email:
16
48
  - martin.rhoads@airbnb.com
17
- executables: []
49
+ executables:
50
+ - stem
18
51
  extensions: []
19
52
  extra_rdoc_files: []
20
53
  files:
@@ -23,7 +56,9 @@ files:
23
56
  - LICENSE.txt
24
57
  - README.md
25
58
  - Rakefile
59
+ - bin/stem
26
60
  - lib/stemcell.rb
61
+ - lib/stemcell/templates/bootstrap.sh.erb
27
62
  - lib/stemcell/version.rb
28
63
  - stemcell.gemspec
29
64
  homepage: ''
@@ -49,5 +84,5 @@ rubyforge_project:
49
84
  rubygems_version: 1.8.23
50
85
  signing_key:
51
86
  specification_version: 3
52
- summary: Write a gem summary
87
+ summary: no summary
53
88
  test_files: []