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.
Files changed (50) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/HISTORY.md +8 -0
  4. data/README.md +65 -0
  5. data/Rakefile +27 -0
  6. data/buckknife.gemspec +31 -0
  7. data/lib/buckknife.rb +44 -0
  8. data/lib/buckknife/command.rb +33 -0
  9. data/lib/buckknife/commands/add_role.rb +13 -0
  10. data/lib/buckknife/commands/base.rb +24 -0
  11. data/lib/buckknife/commands/bootstrap.rb +28 -0
  12. data/lib/buckknife/commands/capistrano.rb +46 -0
  13. data/lib/buckknife/commands/create.rb +42 -0
  14. data/lib/buckknife/commands/euca_create.rb +60 -0
  15. data/lib/buckknife/commands/rackspace_create.rb +58 -0
  16. data/lib/buckknife/commands/run_client.rb +49 -0
  17. data/lib/buckknife/data_accessor.rb +29 -0
  18. data/lib/buckknife/environment.rb +16 -0
  19. data/lib/buckknife/helper.rb +39 -0
  20. data/lib/buckknife/knife_helper.rb +22 -0
  21. data/lib/buckknife/node.rb +30 -0
  22. data/lib/buckknife/project.rb +80 -0
  23. data/lib/buckknife/templates/deploy_environments.rb.erb +85 -0
  24. data/lib/buckknife/templates/project.json.erb +75 -0
  25. data/lib/buckknife/version.rb +3 -0
  26. data/lib/chef/knife/project_add_role.rb +24 -0
  27. data/lib/chef/knife/project_bootstrap.rb +24 -0
  28. data/lib/chef/knife/project_capistrano.rb +19 -0
  29. data/lib/chef/knife/project_create.rb +53 -0
  30. data/lib/chef/knife/project_info.rb +13 -0
  31. data/lib/chef/knife/project_list.rb +17 -0
  32. data/lib/chef/knife/project_new.rb +28 -0
  33. data/lib/chef/knife/project_run_client.rb +30 -0
  34. data/lib/chef/knife/project_show.rb +34 -0
  35. data/spec/buckknife/commands/add_role_spec.rb +14 -0
  36. data/spec/buckknife/commands/bootstrap_spec.rb +22 -0
  37. data/spec/buckknife/commands/capistrano_spec.rb +13 -0
  38. data/spec/buckknife/commands/create_spec.rb +48 -0
  39. data/spec/buckknife/commands/run_client_spec.rb +15 -0
  40. data/spec/buckknife/environment_spec.rb +14 -0
  41. data/spec/buckknife/helper_spec.rb +11 -0
  42. data/spec/buckknife/node_spec.rb +18 -0
  43. data/spec/buckknife/project_spec.rb +49 -0
  44. data/spec/data_bags/projects/myapp.json +139 -0
  45. data/spec/knife.rb +4 -0
  46. data/spec/knife/project_bootstrap_spec.rb +0 -0
  47. data/spec/knife/project_info_spec.rb +17 -0
  48. data/spec/knife/project_list_spec.rb +18 -0
  49. data/spec/spec_helper.rb +27 -0
  50. metadata +251 -0
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.DS_Store*
6
+ coverage*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in buckknife.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ ## 0.0.4 (2011-07-22)
2
+
3
+ * Add `knife project new PROJECT` command.
4
+ * Add `knife project add role PROJECT [NODE]` command.
5
+
6
+ ## 0.0.3 (2011-07-22)
7
+
8
+ * Initial open sourcing of AKQA's internal chef tool.
@@ -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
@@ -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
@@ -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
@@ -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,13 @@
1
+ module BuckKnife
2
+ module Commands
3
+ class AddRole < Struct.new(:node)
4
+ include Base
5
+
6
+ def command
7
+ node.run_list_items.map do |item|
8
+ Command.new('knife').arg('node', 'run_list', 'add', node.name, item)
9
+ end.join("\n")
10
+ end
11
+ end
12
+ end
13
+ 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