stemcell 0.0.1 → 0.0.3

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/.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: []