roket 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/bin/roket +113 -0
- data/lib/roket.rb +131 -2
- data/lib/roket/templates/bootstrap.sh.erb +130 -0
- data/lib/roket/version.rb +1 -1
- data/roket.gemspec +3 -0
- metadata +39 -4
data/.gitignore
CHANGED
data/bin/roket
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# -*- mode: shell -*-
|
4
|
+
|
5
|
+
require_relative '../lib/roket'
|
6
|
+
|
7
|
+
require 'trollop'
|
8
|
+
|
9
|
+
options = Trollop::options do
|
10
|
+
version "roket #{Roket::VERSION} (c) 2013 Martin Rhoads"
|
11
|
+
banner <<END_OF_BANNER
|
12
|
+
welcome to roket
|
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
|
+
roket = Roket::Roket.new(options)
|
112
|
+
roket.launch({'count' => options['count']})
|
113
|
+
|
data/lib/roket.rb
CHANGED
@@ -1,5 +1,134 @@
|
|
1
|
-
require
|
1
|
+
require 'logger'
|
2
|
+
require 'erb'
|
3
|
+
require 'aws-sdk'
|
4
|
+
|
5
|
+
require_relative "roket/version"
|
2
6
|
|
3
7
|
module Roket
|
4
|
-
|
8
|
+
class Roket
|
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
|
+
raise "\ncould not open specified key #{@chef_validation_key}:\n#{e.inspect}#{e.backtrace}"
|
36
|
+
end
|
37
|
+
|
38
|
+
if opts['chef_data_bag_secret']
|
39
|
+
begin
|
40
|
+
@chef_data_bag_secret = File.read(opts['chef_data_bag_secret'])
|
41
|
+
rescue Object => e
|
42
|
+
raise "\ncould not open specified secret key file #{opts['chef_data_bag_secret']}:\n#{e.inspect}#{e.backtrace}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
@chef_data_bag_secret = ''
|
46
|
+
end
|
47
|
+
|
48
|
+
AWS.config({:access_key_id => @aws_access_key, :secret_access_key => @aws_secret_key})
|
49
|
+
@ec2 = AWS::EC2.new(:ec2_endpoint => @ec2_url)
|
50
|
+
@ec2_region = @ec2.regions[@region]
|
51
|
+
|
52
|
+
@user_data = render_template
|
53
|
+
end
|
54
|
+
|
55
|
+
def launch(opts={})
|
56
|
+
File.open('/tmp/user-data', 'w') {|f| f.write(@user_data) }
|
57
|
+
instances = do_launch(opts)
|
58
|
+
wait(instances)
|
59
|
+
print_run_info(instances)
|
60
|
+
print_config_info
|
61
|
+
return instances
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def print_config_info
|
67
|
+
puts "install logs will be in /var/log/init and /var/log/init.err"
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_run_info(instances)
|
71
|
+
puts "here is the info for what's launched:"
|
72
|
+
instances.each do |instance|
|
73
|
+
puts "\tinstance_id: #{instance.instance_id}"
|
74
|
+
puts "\tpublic ip: #{instance.public_ip_address}"
|
75
|
+
puts
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def wait(instances)
|
80
|
+
sleep 3
|
81
|
+
while true
|
82
|
+
if Time.now - @start_time > @timeout
|
83
|
+
bail(instances)
|
84
|
+
raise TimeoutError, "exceded timeout of #{@timeout}"
|
85
|
+
end
|
86
|
+
puts "instances is #{instances.inspect}"
|
87
|
+
if instances.select{|i| i.status != :running }.empty?
|
88
|
+
@log.info "all instances in running state"
|
89
|
+
return
|
90
|
+
end
|
91
|
+
@log.info "instances not ready yet. sleeping..."
|
92
|
+
sleep 5
|
93
|
+
return wait(instances)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_launch(opts={})
|
98
|
+
options = {
|
99
|
+
:image_id => @image,
|
100
|
+
:security_groups => @security_group,
|
101
|
+
:user_data => @user_data,
|
102
|
+
:instance_type => @machine_type,
|
103
|
+
:key_name => @key_name,
|
104
|
+
}
|
105
|
+
options.merge!({:availability_zone => opts['avilibility_zone']}) if opts['availability_zone']
|
106
|
+
options.merge!({:count => opts['count']}) if opts['count']
|
107
|
+
puts "creating instance with options:\n#{options}"
|
108
|
+
instances = @ec2_region.instances.create(options)
|
109
|
+
instances = [instances] unless instances.class == Array
|
110
|
+
instances.each do |instance|
|
111
|
+
@log.info "launched instance #{instance.instance_id}"
|
112
|
+
end
|
113
|
+
return instances
|
114
|
+
end
|
115
|
+
|
116
|
+
def render_template
|
117
|
+
this_file = File.expand_path __FILE__
|
118
|
+
base_dir = File.dirname this_file
|
119
|
+
template_file_path = File.join(base_dir,'roket','templates','bootstrap.sh.erb')
|
120
|
+
template_file = File.read(template_file_path)
|
121
|
+
erb_template = ERB.new(template_file)
|
122
|
+
generated_template = erb_template.result(binding)
|
123
|
+
return generated_template
|
124
|
+
end
|
125
|
+
|
126
|
+
def bail(instances)
|
127
|
+
return if instances.nil?
|
128
|
+
instances.each do |instance|
|
129
|
+
instance.delete
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
5
134
|
end
|
@@ -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:)"
|
data/lib/roket/version.rb
CHANGED
data/roket.gemspec
CHANGED
@@ -16,4 +16,7 @@ Gem::Specification.new do |gem|
|
|
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: roket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
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-
|
13
|
-
dependencies:
|
12
|
+
date: 2013-02-15 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'
|
14
46
|
description: roket launches instances
|
15
47
|
email:
|
16
48
|
- martin.rhoads@airbnb.com
|
17
|
-
executables:
|
49
|
+
executables:
|
50
|
+
- roket
|
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/roket
|
26
60
|
- lib/roket.rb
|
61
|
+
- lib/roket/templates/bootstrap.sh.erb
|
27
62
|
- lib/roket/version.rb
|
28
63
|
- roket.gemspec
|
29
64
|
homepage: ''
|