roket 0.0.1 → 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/.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: ''
|