buckknife 0.0.4
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 +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
|