buckknife 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/HISTORY.md +8 -0
- data/README.md +65 -0
- data/Rakefile +27 -0
- data/buckknife.gemspec +31 -0
- data/lib/buckknife.rb +44 -0
- data/lib/buckknife/command.rb +33 -0
- data/lib/buckknife/commands/add_role.rb +13 -0
- data/lib/buckknife/commands/base.rb +24 -0
- data/lib/buckknife/commands/bootstrap.rb +28 -0
- data/lib/buckknife/commands/capistrano.rb +46 -0
- data/lib/buckknife/commands/create.rb +42 -0
- data/lib/buckknife/commands/euca_create.rb +60 -0
- data/lib/buckknife/commands/rackspace_create.rb +58 -0
- data/lib/buckknife/commands/run_client.rb +49 -0
- data/lib/buckknife/data_accessor.rb +29 -0
- data/lib/buckknife/environment.rb +16 -0
- data/lib/buckknife/helper.rb +39 -0
- data/lib/buckknife/knife_helper.rb +22 -0
- data/lib/buckknife/node.rb +30 -0
- data/lib/buckknife/project.rb +80 -0
- data/lib/buckknife/templates/deploy_environments.rb.erb +85 -0
- data/lib/buckknife/templates/project.json.erb +75 -0
- data/lib/buckknife/version.rb +3 -0
- data/lib/chef/knife/project_add_role.rb +24 -0
- data/lib/chef/knife/project_bootstrap.rb +24 -0
- data/lib/chef/knife/project_capistrano.rb +19 -0
- data/lib/chef/knife/project_create.rb +53 -0
- data/lib/chef/knife/project_info.rb +13 -0
- data/lib/chef/knife/project_list.rb +17 -0
- data/lib/chef/knife/project_new.rb +28 -0
- data/lib/chef/knife/project_run_client.rb +30 -0
- data/lib/chef/knife/project_show.rb +34 -0
- data/spec/buckknife/commands/add_role_spec.rb +14 -0
- data/spec/buckknife/commands/bootstrap_spec.rb +22 -0
- data/spec/buckknife/commands/capistrano_spec.rb +13 -0
- data/spec/buckknife/commands/create_spec.rb +48 -0
- data/spec/buckknife/commands/run_client_spec.rb +15 -0
- data/spec/buckknife/environment_spec.rb +14 -0
- data/spec/buckknife/helper_spec.rb +11 -0
- data/spec/buckknife/node_spec.rb +18 -0
- data/spec/buckknife/project_spec.rb +49 -0
- data/spec/data_bags/projects/myapp.json +139 -0
- data/spec/knife.rb +4 -0
- data/spec/knife/project_bootstrap_spec.rb +0 -0
- data/spec/knife/project_info_spec.rb +17 -0
- data/spec/knife/project_list_spec.rb +18 -0
- data/spec/spec_helper.rb +27 -0
- metadata +251 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/HISTORY.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# BuckKnife
|
2
|
+
|
3
|
+
<img alt="Not a Knife" src="http://img.metro.co.uk/i/pix/2010/08/26/article-1282853100512-00A2E06C00000259-351343_636x300.jpg" />
|
4
|
+
|
5
|
+
BuckKnife adds a `knife project` subcommand to Chef's `knife`.
|
6
|
+
|
7
|
+
## Caveats
|
8
|
+
|
9
|
+
BuckKnife is undergoing active development. A 1.0 version will be released
|
10
|
+
when we think the project is in a stable state. Until then, this is alpha
|
11
|
+
quality.
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
Add these configurations to your `.chef/knife.rb`:
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
knife[:project_dir] = "path/to/chef-rep/data_bags/projects"
|
19
|
+
```
|
20
|
+
|
21
|
+
Here is a complete `~/.chef/knife.rb` example:
|
22
|
+
|
23
|
+
``` ruby
|
24
|
+
chef_repo_dir = "~/Chef"
|
25
|
+
|
26
|
+
log_level :info
|
27
|
+
log_location STDOUT
|
28
|
+
interval 300
|
29
|
+
client_key "~/.chef/clientkey.pem"
|
30
|
+
validation_client_name "chef-validator"
|
31
|
+
validation_key "#{chef_repo_dir}/.chef/validation.pem"
|
32
|
+
chef_server_url "http://chefserver.example.com"
|
33
|
+
cache_type "BasicFile"
|
34
|
+
cache_options( :path => "#{chef_repo_dir}/.chef/checksums" )
|
35
|
+
cookbook_path "#{chef_repo_dir}/cookbooks"
|
36
|
+
knife[:project_dir] = "#{chef_repo_dir}/data_bags/projects"
|
37
|
+
```
|
38
|
+
|
39
|
+
## Getting Started
|
40
|
+
|
41
|
+
This is the current process to create chef server clusters based on a project
|
42
|
+
.json file. There are many improvements that need to be made to make this more
|
43
|
+
automated.
|
44
|
+
|
45
|
+
1. Edit your ~/.chef/knife.rb to include your project dir
|
46
|
+
2. Create a project data bag:
|
47
|
+
|
48
|
+
knife project new myapp
|
49
|
+
|
50
|
+
3. Generate create commands
|
51
|
+
|
52
|
+
knife project create myapp
|
53
|
+
|
54
|
+
4. Generate add role commands
|
55
|
+
|
56
|
+
knife project add role myapp
|
57
|
+
|
58
|
+
5. Generate run client commands
|
59
|
+
|
60
|
+
knife project run client myapp
|
61
|
+
|
62
|
+
## TODO
|
63
|
+
|
64
|
+
* Move database passwords out of project.json
|
65
|
+
* Move ssh user/pass out of capistrano template
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'buckknife/version'
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
t.rspec_opts = %w[--color --format nested]
|
8
|
+
t.verbose = false
|
9
|
+
end
|
10
|
+
|
11
|
+
if RUBY_VERSION.to_f == 1.8
|
12
|
+
namespace :rcov do
|
13
|
+
task :cleanup do
|
14
|
+
rm_rf 'coverage.data'
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec::Core::RakeTask.new :spec do |t|
|
18
|
+
t.rcov = true
|
19
|
+
t.rcov_opts = %[-Ilib -Ispec --exclude "gems/*,features"]
|
20
|
+
t.rcov_opts << %[--no-html --aggregate coverage.data]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
task :rcov => ["rcov:cleanup", "rcov:spec"]
|
25
|
+
end
|
26
|
+
|
27
|
+
task :default => :spec
|
data/buckknife.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "buckknife/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "buckknife"
|
7
|
+
s.version = BuckKnife::VERSION
|
8
|
+
s.authors = ["Ben Marini", "Kirt Fitzpatrick"]
|
9
|
+
s.email = ["ben.marini@akqa.com", "kirt.fitzpatrick@akqa.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{You call that a knife? THIS is a knife}
|
12
|
+
s.description = %q{Extras for Chef's knife command}
|
13
|
+
|
14
|
+
s.rubyforge_project = "buckknife"
|
15
|
+
|
16
|
+
s.add_dependency "chef", "~> 0.10.2"
|
17
|
+
s.add_dependency "highline"
|
18
|
+
s.add_dependency "json"
|
19
|
+
s.add_dependency "escape"
|
20
|
+
s.add_dependency "activemodel", "~> 3.0.9"
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
23
|
+
s.add_development_dependency "rcov", "~> 0.9.9"
|
24
|
+
s.add_development_dependency "knife-rackspace"
|
25
|
+
# s.add_development_dependency "knife-eucalyptus"
|
26
|
+
|
27
|
+
s.files = `git ls-files`.split("\n")
|
28
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
29
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
end
|
data/lib/buckknife.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'buckknife/version'
|
2
|
+
require 'highline'
|
3
|
+
require 'json'
|
4
|
+
require 'erb'
|
5
|
+
require 'chef'
|
6
|
+
|
7
|
+
module BuckKnife
|
8
|
+
autoload :DataAccessor, 'buckknife/data_accessor'
|
9
|
+
autoload :Environment, 'buckknife/environment'
|
10
|
+
autoload :Helper, 'buckknife/helper'
|
11
|
+
autoload :Node, 'buckknife/node'
|
12
|
+
autoload :Project, 'buckknife/project'
|
13
|
+
|
14
|
+
autoload :KnifeHelper, 'buckknife/knife_helper'
|
15
|
+
autoload :ProjectBootstrap, 'chef/knife/project_bootstrap'
|
16
|
+
autoload :ProjectNew, 'chef/knife/project_new'
|
17
|
+
autoload :ProjectCapistrano, 'chef/knife/project_capistrano'
|
18
|
+
autoload :ProjectCreate, 'chef/knife/project_create'
|
19
|
+
autoload :ProjectInfo, 'chef/knife/project_info'
|
20
|
+
autoload :ProjectList, 'chef/knife/project_list'
|
21
|
+
autoload :ProjectRunClient, 'chef/knife/project_run_client'
|
22
|
+
|
23
|
+
autoload :Command, 'buckknife/command'
|
24
|
+
|
25
|
+
module Commands
|
26
|
+
autoload :AddRole, 'buckknife/commands/add_role'
|
27
|
+
autoload :Base, 'buckknife/commands/base'
|
28
|
+
autoload :Bootstrap, 'buckknife/commands/bootstrap'
|
29
|
+
autoload :Capistrano, 'buckknife/commands/capistrano'
|
30
|
+
autoload :Create, 'buckknife/commands/create'
|
31
|
+
autoload :RackspaceCreate, 'buckknife/commands/rackspace_create'
|
32
|
+
autoload :EucaCreate, 'buckknife/commands/euca_create'
|
33
|
+
autoload :RunClient, 'buckknife/commands/run_client'
|
34
|
+
end
|
35
|
+
|
36
|
+
class BuckKnifeError < StandardError; end
|
37
|
+
class UnknownProjectRootError < BuckKnifeError; end
|
38
|
+
class UnknownProjectError < BuckKnifeError; end
|
39
|
+
class UnknownDataFieldError < BuckKnifeError; end
|
40
|
+
|
41
|
+
def self.template_root
|
42
|
+
Pathname.new( File.expand_path("../buckknife/templates", __FILE__) )
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'escape'
|
2
|
+
|
3
|
+
module BuckKnife
|
4
|
+
class Command
|
5
|
+
def initialize(cmd="")
|
6
|
+
@cmd = cmd
|
7
|
+
@args, @opts = [], []
|
8
|
+
end
|
9
|
+
|
10
|
+
def arg(*args)
|
11
|
+
@args += args
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def opt(*opts)
|
16
|
+
@opts += opts
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
[ @cmd ].concat(@args).concat(@opts).map(&:to_s).map { |w| escape(w) }.join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def exec
|
25
|
+
`#{self.to_s}`
|
26
|
+
end
|
27
|
+
|
28
|
+
# http://stackoverflow.com/questions/1306680/shellwords-shellescape-implementation-for-ruby-1-8
|
29
|
+
def escape(str)
|
30
|
+
Escape.shell_single_word(str).to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BuckKnife
|
2
|
+
module Commands
|
3
|
+
module Base
|
4
|
+
|
5
|
+
def command
|
6
|
+
Command.new 'knife'
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
command.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def base_run_list
|
14
|
+
project.base_run_list
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_run_list(roles_and_recipes)
|
18
|
+
roles_and_recipes.map do |type, name|
|
19
|
+
"%s[%s]" % [type, name]
|
20
|
+
end.join(',')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BuckKnife
|
2
|
+
module Commands
|
3
|
+
# Takes a Project and Node instance
|
4
|
+
# Returns the knife command to bootstrap this server
|
5
|
+
#
|
6
|
+
# NOTE: This does not add the roles and recipes of the node. This is
|
7
|
+
# because currently there are some dependency issues across nodes with
|
8
|
+
# some recipes. The current strategy is to add roles manually one by one
|
9
|
+
# running the chef client as needed.
|
10
|
+
class Bootstrap < Struct.new(:project, :node)
|
11
|
+
include Base
|
12
|
+
|
13
|
+
def command
|
14
|
+
super.tap do |c|
|
15
|
+
c.arg 'bootstrap'
|
16
|
+
c.arg node.address
|
17
|
+
c.opt '-N', node.name
|
18
|
+
c.opt '-x', node.ssh_username
|
19
|
+
c.opt '-P', node.ssh_password
|
20
|
+
c.opt '-r', base_run_list
|
21
|
+
c.opt '-d', project.distro
|
22
|
+
c.opt '--sudo'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module BuckKnife
|
4
|
+
module Commands
|
5
|
+
class Capistrano < Struct.new(:project)
|
6
|
+
include Base
|
7
|
+
|
8
|
+
def call
|
9
|
+
environments = []
|
10
|
+
project.environments.each do |env|
|
11
|
+
|
12
|
+
app_nodes = env.nodes.select { |n| n.group == "app" }.each { |n| n.address = fetch_ip(n.name) }
|
13
|
+
job_nodes = env.nodes.select { |n| n.group == "job" }.each { |n| n.address = fetch_ip(n.name) }
|
14
|
+
|
15
|
+
unless app_nodes.empty? && job_nodes.empty?
|
16
|
+
environment = {:app_servers => app_nodes, :job_servers => job_nodes}
|
17
|
+
environment[:primary_db] = ( job_nodes.empty? ? app_nodes.first : job_nodes.first )
|
18
|
+
environment[:name] = env.name
|
19
|
+
environment[:domain_name] = env.domain_name
|
20
|
+
environments << environment
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
deploy_environments_template = BuckKnife.template_root.join("deploy_environments.rb.erb").read
|
25
|
+
return ERB.new(deploy_environments_template, nil, '%>').result(binding)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def fetch_ip(node_name)
|
30
|
+
cmd = Command.new "bundle"
|
31
|
+
cmd.arg "exec", "knife", "node", "show"
|
32
|
+
cmd.arg node_name
|
33
|
+
cmd.opt "--attribute", "ipaddress"
|
34
|
+
cmd.opt "-Fj"
|
35
|
+
|
36
|
+
JSON.parse( cmd.exec )['ipaddress'] rescue nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def ssh(node_name)
|
40
|
+
ipaddress_cmd = fetch_ip(node_name)
|
41
|
+
ipaddress = JSON.parse `#{ipaddress_cmd}`
|
42
|
+
exec "ssh #{ipaddress['ipaddress']}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BuckKnife
|
2
|
+
module Commands
|
3
|
+
class Create < Struct.new(:project, :node)
|
4
|
+
include Base
|
5
|
+
|
6
|
+
def self.[](provider)
|
7
|
+
case provider
|
8
|
+
when "rackspace" then RackspaceCreate
|
9
|
+
when "euca" then EucaCreate
|
10
|
+
else
|
11
|
+
raise ArgumentError, "Buckknife doesn't know how to create for this provider"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def command
|
16
|
+
Command.new('knife').tap do |c|
|
17
|
+
c.arg project.provider, 'server', 'create'
|
18
|
+
c.opt '-r', base_run_list
|
19
|
+
c.opt '-f', flavor
|
20
|
+
c.opt '-I', image_id
|
21
|
+
c.opt '-d', project.distro
|
22
|
+
c.opt '-s', chef_server_url
|
23
|
+
c.opt '-N', node.name
|
24
|
+
c.opt '-i', project.identity_file if project.identity_file
|
25
|
+
c.opt '-x', project.ssh_username if project.ssh_username
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def image_id
|
30
|
+
node.image_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def flavor
|
34
|
+
node.flavor
|
35
|
+
end
|
36
|
+
|
37
|
+
def chef_server_url
|
38
|
+
Chef::Config[:chef_server_url]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module BuckKnife
|
2
|
+
module Commands
|
3
|
+
class EucaCreate < Create
|
4
|
+
include Base
|
5
|
+
|
6
|
+
# -Z, --availability-zone ZONE The Availability Zone
|
7
|
+
# --bootstrap-version VERSION The version of Chef to install
|
8
|
+
# -N, --node-name NAME The Chef node name for your new node
|
9
|
+
# -s, --server-url URL Chef Server URL
|
10
|
+
# -k, --key KEY API Client Key
|
11
|
+
# --color Use colored output
|
12
|
+
# -c, --config CONFIG The configuration file to use
|
13
|
+
# --defaults Accept default values for all questions
|
14
|
+
# -d, --distro DISTRO Bootstrap a distro using a template
|
15
|
+
# -e, --editor EDITOR Set the editor to use for interactive commands
|
16
|
+
# -E, --environment ENVIRONMENT Set the Chef environment
|
17
|
+
# -f, --flavor FLAVOR The flavor of server (m1.small, m1.medium, etc)
|
18
|
+
# -F, --format FORMAT Which format to use for output
|
19
|
+
# -i IDENTITY_FILE The SSH identity file used for authentication
|
20
|
+
# --identity-file
|
21
|
+
# -I, --image IMAGE The AMI for the server
|
22
|
+
# --no-color Don't use colors in the output
|
23
|
+
# -n, --no-editor Do not open EDITOR, just accept the data as is
|
24
|
+
# --no-host-key-verify Disable host key verification
|
25
|
+
# -u, --user USER API Client Username
|
26
|
+
# --prerelease Install the pre-release chef gems
|
27
|
+
# --print-after Show the data after a destructive operation
|
28
|
+
# -r, --run-list RUN_LIST Comma separated list of roles/recipes to apply
|
29
|
+
# -G, --groups X,Y,Z The security groups for this server
|
30
|
+
# -S, --ssh-key KEY The Eucalyptus SSH key id
|
31
|
+
# -P, --ssh-password PASSWORD The ssh password
|
32
|
+
# -x, --ssh-user USERNAME The ssh username
|
33
|
+
# --template-file TEMPLATE Full path to location of template to use
|
34
|
+
# -V, --verbose More verbose output. Use twice for max verbosity
|
35
|
+
# -v, --version Show chef version
|
36
|
+
# -y, --yes Say yes to all prompts for confirmation
|
37
|
+
# -h, --help Show this message
|
38
|
+
|
39
|
+
def command
|
40
|
+
super.tap do |c|
|
41
|
+
c.opt '-Z', availability_zone if availability_zone
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def image_id
|
48
|
+
super || "emi-DFB9107F"
|
49
|
+
end
|
50
|
+
|
51
|
+
def flavor
|
52
|
+
super || "m1.large"
|
53
|
+
end
|
54
|
+
|
55
|
+
def availability_zone
|
56
|
+
Chef::Config[:region]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|