toquen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +87 -0
- data/Rakefile +9 -0
- data/lib/toquen.rb +17 -0
- data/lib/toquen/aws.rb +35 -0
- data/lib/toquen/capistrano.rb +141 -0
- data/lib/toquen/templates/deploy.rb +14 -0
- data/lib/toquen/version.rb +3 -0
- data/toquen.gemspec +22 -0
- metadata +103 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brian Muller
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Toquen
|
2
|
+
|
3
|
+
**Toquen** combines [Capistrano 3](http://www.capistranorb.com), [Chef](http://www.getchef.com), and [AWS instance tags](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html) into one bundle of joy. Instance roles are stored in AWS tags and **Toquen** can suck those out, put them into data bags for chef, and create stages in capistrano. You can then selectively run chef on individual servers or whole roles that contain many servers with simple commands.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Generally, it's easiest if you start off in an empty directory. First, create a file named *Gemfile* that contains these lines:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
source 'http://rubygems.org'
|
11
|
+
gem 'toquen'
|
12
|
+
```
|
13
|
+
|
14
|
+
Then, create a file named *Capfile* that contains the following line:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
require 'toquen'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then on the command line execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
$ cap toquen_install
|
24
|
+
|
25
|
+
This will create a config directory with a file named *deploy.rb*. Edit this file, setting the location of your AWS key, AWS credentials, and chef cookbooks/data bags/roles.
|
26
|
+
|
27
|
+
Then, in AWS, create an [AWS instance tag](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html) named "Roles" for each instance, using a space separated list of chef roles as the value. The "Name" tag must also be set or the instance will be ignored.
|
28
|
+
|
29
|
+
Then, run:
|
30
|
+
|
31
|
+
$ cap update_roles
|
32
|
+
|
33
|
+
This will create a data_bag named *servers* that contains one item per server name, as well as create stages per server and role for use in capistrano.
|
34
|
+
|
35
|
+
## Server Bootstrapping
|
36
|
+
Bootstrapping a server will perform all of the following:
|
37
|
+
|
38
|
+
1. Update all packages (assuming a Debian/Ubuntu system)
|
39
|
+
1. Set ruby 1.9.3 as the default ruby
|
40
|
+
1. Install rubygems
|
41
|
+
1. Install the chef and bundler gems
|
42
|
+
1. Reboot
|
43
|
+
|
44
|
+
You can bootstrap a single server by using:
|
45
|
+
|
46
|
+
$ cap server-<server name> update_roles
|
47
|
+
|
48
|
+
Or a all the servers with a given role with:
|
49
|
+
|
50
|
+
$ cap <role name> update_roles
|
51
|
+
|
52
|
+
A lockfile is created after the first bootstrapping so that the full bootstrap process is only run once per server.
|
53
|
+
|
54
|
+
## Running Chef-Solo
|
55
|
+
You can run chef-solo for a single server by using:
|
56
|
+
|
57
|
+
$ cap server-<server name> cook
|
58
|
+
|
59
|
+
Or a all the servers with a given role with:
|
60
|
+
|
61
|
+
$ cap <role name> cook
|
62
|
+
|
63
|
+
## Updating Roles
|
64
|
+
If you change the roles of any servers you will need to run:
|
65
|
+
|
66
|
+
$ bundle exec cap update_roles
|
67
|
+
|
68
|
+
This will update the *servers* data_bag as well as the capistrano stages.
|
69
|
+
|
70
|
+
## Additional Configuration
|
71
|
+
If you want to use a different tag name (or you like commas as a delimiter) you can specify your own role extractor by placing the following in either your Capfile or config/deploy.rb:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
Toquen.config.aws_roles_extractor = lambda { |inst| (inst.tags["MyRoles"] || "").split(",") }
|
75
|
+
```
|
76
|
+
|
77
|
+
By default, instance information is only pulled out of the default region (us-east-1), but you can specify mutiple alternative regions:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
set :aws_regions, ['us-west-1', 'us-west-2']
|
81
|
+
```
|
82
|
+
|
83
|
+
You can also manually specify the version of rubygems you want installed (default is 2.1.11):
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
set :rubygems_version, "2.1.11"
|
87
|
+
```
|
data/Rakefile
ADDED
data/lib/toquen.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "toquen/version"
|
2
|
+
require "toquen/aws"
|
3
|
+
require "toquen/capistrano"
|
4
|
+
|
5
|
+
module Toquen
|
6
|
+
class Config
|
7
|
+
attr_accessor :aws_roles_extractor
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@aws_roles_extractor = lambda { |inst| (inst.tags["Roles"] || "").split }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@config ||= Config.new
|
16
|
+
end
|
17
|
+
end
|
data/lib/toquen/aws.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'aws'
|
2
|
+
|
3
|
+
module Toquen
|
4
|
+
class AWSProxy
|
5
|
+
def initialize
|
6
|
+
@key_id = fetch(:aws_access_key_id)
|
7
|
+
@key = fetch(:aws_secret_access_key)
|
8
|
+
@regions = fetch(:aws_regions, ['us-east-1'])
|
9
|
+
end
|
10
|
+
|
11
|
+
def server_details
|
12
|
+
filter @regions.map { |region| server_details_in(region) }.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(details)
|
16
|
+
details.select { |detail|
|
17
|
+
not detail[:name].nil? and detail[:roles].length > 0
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def server_details_in(region)
|
22
|
+
AWS.config(:access_key_id => @key_id, :secret_access_key => @key, :region => region)
|
23
|
+
AWS::EC2.new.instances.map do |i|
|
24
|
+
{
|
25
|
+
:id => i.tags["Name"],
|
26
|
+
:internal_ip => i.private_ip_address,
|
27
|
+
:external_ip => i.public_ip_address,
|
28
|
+
:name => i.tags["Name"],
|
29
|
+
:roles => Toquen.config.aws_roles_extractor.call(i)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'capistrano/setup'
|
2
|
+
require 'capistrano/console'
|
3
|
+
|
4
|
+
desc "update local cache of servers and roles"
|
5
|
+
task :update_roles do
|
6
|
+
load Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
|
7
|
+
roles = Hash.new([])
|
8
|
+
|
9
|
+
aws = Toquen::AWSProxy.new
|
10
|
+
aws.server_details.each do |details|
|
11
|
+
run_locally { info "Updating local details for #{details[:name]} (#{details[:external_ip]})" }
|
12
|
+
|
13
|
+
open("#{fetch(:chef_data_bags_path)}/servers/#{details[:name]}.json", 'w') { |f|
|
14
|
+
f.write JSON.dump(details)
|
15
|
+
}
|
16
|
+
details[:roles].each { |role| roles[role] += [details[:external_ip]] }
|
17
|
+
roles['all'] += [details[:external_ip]]
|
18
|
+
|
19
|
+
open("config/deploy/server-#{details[:name]}.rb", 'w') { |f|
|
20
|
+
f.write("# This file will be overwritten by toquen! Don't put anything here.\n")
|
21
|
+
f.write("set :stage, 'server-#{details[:name]}'.intern\n")
|
22
|
+
(details[:roles] + ["server-#{details[:name]}"]).each { |role|
|
23
|
+
f.write("role '#{role}'.intern, %w{#{details[:external_ip]}}\n")
|
24
|
+
}
|
25
|
+
f.write("set :filter, :roles => %w{server-#{details[:name]}}\n")
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
roles.keys.each do |name|
|
30
|
+
open("config/deploy/#{name}.rb", 'w') { |f|
|
31
|
+
f.write("# This file will be overwritten by toquen! Don't put anything here.\n")
|
32
|
+
f.write("set :stage, '#{name}'.intern\n")
|
33
|
+
roles.each { |n,ips|
|
34
|
+
f.write("role '#{n}'.intern, %w{#{ips.reject(&:nil?).join(' ')}}\n")
|
35
|
+
}
|
36
|
+
f.write("set :filter, :roles => %w{#{name}}\n")
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "bootstrap a server so that it can run chef"
|
42
|
+
task :bootstrap do
|
43
|
+
rgems = "rubygems-#{fetch(:rubygems_version, '2.1.11')}"
|
44
|
+
on roles(:all), in: :parallel do |host|
|
45
|
+
info "Bootstrapping #{host}..."
|
46
|
+
code = <<-EOF
|
47
|
+
#!/bin/bash
|
48
|
+
if [ -e "/home/#{fetch(:ssh_options)[:user]}/bootstrap.lock" ]; then exit 0; fi
|
49
|
+
DEBIAN_FRONTEND=noninteractive apt-get -y update
|
50
|
+
DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
|
51
|
+
DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade
|
52
|
+
DEBIAN_FRONTEND=noninteractive apt-get -y install ruby1.9.3 ruby-dev automake make
|
53
|
+
update-alternatives --set ruby /usr/bin/ruby1.9.1
|
54
|
+
cd /usr/src
|
55
|
+
rm -rf rubygems*
|
56
|
+
wget -q http://production.cf.rubygems.org/rubygems/#{rgems}.tgz
|
57
|
+
tar -zxf #{rgems}.tgz
|
58
|
+
cd /usr/src/#{rgems}
|
59
|
+
ruby setup.rb
|
60
|
+
gem install --no-rdoc --no-ri chef bundler
|
61
|
+
touch /home/#{fetch(:ssh_options)[:user]}/bootstrap.lock
|
62
|
+
echo "Rebooting now, standby..."
|
63
|
+
reboot
|
64
|
+
EOF
|
65
|
+
fname = "/home/#{fetch(:ssh_options)[:user]}/bootstrap.sh"
|
66
|
+
upload! StringIO.new(code), fname
|
67
|
+
sudo "sh #{fname}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Update cookbooks/data bags/roles on server"
|
72
|
+
task :update_kitchen do
|
73
|
+
kitchen = "/home/#{fetch(:ssh_options)[:user]}/kitchen"
|
74
|
+
lkitchen = "/tmp/toquen/kitchen"
|
75
|
+
user = fetch(:ssh_options)[:user]
|
76
|
+
key = fetch(:ssh_options)[:keys].first
|
77
|
+
|
78
|
+
run_locally do
|
79
|
+
info "Building kitchen locally..."
|
80
|
+
execute [
|
81
|
+
"rm -rf #{lkitchen}",
|
82
|
+
"mkdir -p #{lkitchen}",
|
83
|
+
"ln -s #{File.expand_path(fetch(:chef_cookbooks_path))} #{lkitchen}",
|
84
|
+
"ln -s #{File.expand_path(fetch(:chef_data_bags_path))} #{lkitchen}",
|
85
|
+
"ln -s #{File.expand_path(fetch(:chef_roles_path))} #{lkitchen}"
|
86
|
+
].join(" && ")
|
87
|
+
end
|
88
|
+
|
89
|
+
open("#{lkitchen}/chef_config.rb", 'w') { |f|
|
90
|
+
f.write("file_cache_path '/var/chef-solo'\n")
|
91
|
+
f.write("cookbook_path '#{kitchen}/cookbooks'\n")
|
92
|
+
f.write("data_bag_path '#{kitchen}/data_bags'\n")
|
93
|
+
f.write("role_path '#{kitchen}/roles'\n")
|
94
|
+
}
|
95
|
+
|
96
|
+
on roles(:all), in: :parallel do |host|
|
97
|
+
run_locally do
|
98
|
+
info "Sending kitchen to #{host}..."
|
99
|
+
execute "rsync -avzk --delete -e 'ssh -i #{key}' #{lkitchen} #{user}@#{host}:/home/#{fetch(:ssh_options)[:user]}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "Run chef for servers"
|
105
|
+
task :cook do
|
106
|
+
on roles(:all), in: :parallel do |host|
|
107
|
+
info "Chef is now cooking on #{host}..."
|
108
|
+
roles = host.properties.roles.reject { |r| r.to_s.start_with?('server-') }
|
109
|
+
roles = roles.map { |r| "\"role[#{r}]\"" }.join(',')
|
110
|
+
info "Roles for #{host}: #{roles}"
|
111
|
+
tfile = "/home/#{fetch(:ssh_options)[:user]}/chef.json"
|
112
|
+
upload! StringIO.new("{ \"run_list\": [ #{roles} ] }"), tfile
|
113
|
+
execute "sudo chef-solo -c kitchen/chef_config.rb -j #{tfile}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
before :cook, :update_kitchen
|
117
|
+
|
118
|
+
desc "install toquen capistrano setup to current directory"
|
119
|
+
task :toquen_install do
|
120
|
+
unless Dir.exists?('config')
|
121
|
+
puts "Creating config directory..."
|
122
|
+
Dir.mkdir('config')
|
123
|
+
end
|
124
|
+
unless Dir.exists?('config/deploy')
|
125
|
+
puts "Creating config/deploy directory..."
|
126
|
+
Dir.mkdir('config/deploy')
|
127
|
+
end
|
128
|
+
if not File.exists?('config/deploy.rb')
|
129
|
+
puts "Initializing config/deploy.rb configuration file..."
|
130
|
+
FileUtils.cp File.expand_path("../templates/deploy.rb", __FILE__), 'config/deploy.rb'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
module Capistrano
|
135
|
+
module TaskEnhancements
|
136
|
+
alias_method :original_default_tasks, :default_tasks
|
137
|
+
def default_tasks
|
138
|
+
original_default_tasks + %w{toquen_install update_roles}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Set your AWS access key id and secret. This should be provisioned in
|
2
|
+
# Amazon AWS if you haven't already set it up.
|
3
|
+
set :aws_access_key_id, ""
|
4
|
+
set :aws_secret_access_key, ""
|
5
|
+
|
6
|
+
# Set the location of your SSH key. You can give a list of files, but
|
7
|
+
# the first key given will be the one used to upload your chef files to
|
8
|
+
# each server.
|
9
|
+
set :ssh_options, { :keys => ["./mykey.pem"], :user => "ubuntu" }
|
10
|
+
|
11
|
+
# Set the location of your cookbooks/data bags/roles for Chef
|
12
|
+
set :chef_cookbooks_path, 'kitchen/cookbooks'
|
13
|
+
set :chef_data_bags_path, 'kitchen/data_bags'
|
14
|
+
set :chef_roles_path, 'kitchen/roles'
|
data/toquen.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'toquen/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "toquen"
|
8
|
+
gem.version = Toquen::VERSION
|
9
|
+
gem.authors = ["Brian Muller"]
|
10
|
+
gem.email = ["bamuller@gmail.com"]
|
11
|
+
gem.description = "Toquen: Capistrano + AWS + Chef-Solo"
|
12
|
+
gem.summary = "Toquen: Capistrano + AWS + Chef-Solo"
|
13
|
+
gem.homepage = "https://github.com/bmuller/toquen"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_dependency('capistrano', '~> 3.0.1')
|
20
|
+
gem.add_dependency('aws-sdk')
|
21
|
+
gem.add_development_dependency("rdoc")
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: toquen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brian Muller
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2013-12-15 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: capistrano
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: aws-sdk
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rdoc
|
39
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
48
|
+
description: "Toquen: Capistrano + AWS + Chef-Solo"
|
49
|
+
email:
|
50
|
+
- bamuller@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE.txt
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- lib/toquen.rb
|
64
|
+
- lib/toquen/aws.rb
|
65
|
+
- lib/toquen/capistrano.rb
|
66
|
+
- lib/toquen/templates/deploy.rb
|
67
|
+
- lib/toquen/version.rb
|
68
|
+
- toquen.gemspec
|
69
|
+
homepage: https://github.com/bmuller/toquen
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 695670950495976934
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 695670950495976934
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.24
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: "Toquen: Capistrano + AWS + Chef-Solo"
|
102
|
+
test_files: []
|
103
|
+
|