knife-solo 0.0.3

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.
@@ -0,0 +1,80 @@
1
+ require 'pathname'
2
+
3
+ require 'chef/knife'
4
+ require 'chef/config'
5
+
6
+ require 'knife-solo/ssh_command'
7
+ require 'knife-solo/kitchen_command'
8
+
9
+ class Chef
10
+ class Knife
11
+ # Approach ported from spatula (https://github.com/trotter/spatula)
12
+ # Copyright 2009, Trotter Cashion
13
+ class Cook < Knife
14
+ include KnifeSolo::SshCommand
15
+ include KnifeSolo::KitchenCommand
16
+
17
+ banner "knife prepare [user@]hostname [json] (options)"
18
+
19
+ def run
20
+ super
21
+
22
+ check_syntax
23
+
24
+ Chef::Config.from_file('solo.rb')
25
+
26
+ rsync_kitchen
27
+
28
+ add_patches
29
+
30
+ logging_arg = "-l debug" if config[:verbosity] > 0
31
+ stream_command <<-BASH
32
+ sudo chef-solo -c #{chef_path}/solo.rb \
33
+ -j #{chef_path}/#{node_config} \
34
+ #{logging_arg}
35
+ BASH
36
+ end
37
+
38
+ def check_syntax
39
+ Dir["**/*.rb"].each do |recipe|
40
+ ok = system "ruby -c #{recipe} >/dev/null 2>&1"
41
+ raise "Syntax error in #{recipe}" if not ok
42
+ end
43
+
44
+ Dir["**/*.json"].each do |json|
45
+ begin
46
+ require 'json'
47
+ # parse without instantiating Chef classes
48
+ JSON.parse File.read(json), :create_additions => false
49
+ rescue => error
50
+ raise "Syntax error in #{json}: #{error.message}"
51
+ end
52
+ end
53
+ end
54
+
55
+ def node_config
56
+ @name_args[1] || super
57
+ end
58
+
59
+ def chef_path
60
+ Chef::Config.file_cache_path
61
+ end
62
+
63
+ def patch_path
64
+ Array(Chef::Config.cookbook_path).first + "/chef_solo_patches/libraries"
65
+ end
66
+
67
+ # TODO (mat): Let rsync write to /var/chef-solo with sudo somehow
68
+ def rsync_kitchen
69
+ system %Q{rsync -rlP --rsh="ssh #{ssh_args}" --delete --exclude '.*' ./ :#{chef_path}}
70
+ end
71
+
72
+ def add_patches
73
+ run_command "mkdir -p #{patch_path}"
74
+ Dir[Pathname.new(__FILE__).dirname.join("patches", "*.rb")].each do |patch|
75
+ system %Q{rsync -rlP --rsh="ssh #{ssh_args}" #{patch} :#{patch_path}}
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,29 @@
1
+ require 'chef/knife'
2
+
3
+ class Chef
4
+ class Knife
5
+ class Kitchen < Knife
6
+ include FileUtils
7
+
8
+ banner "knife kitchen NAME"
9
+
10
+ def run
11
+ name = @name_args.first
12
+ mkdir name
13
+ mkdir name + "/nodes"
14
+ mkdir name + "/roles"
15
+ mkdir name + "/data_bags"
16
+ mkdir name + "/site-cookbooks"
17
+ mkdir name + "/cookbooks"
18
+ File.open(name + "/solo.rb", 'w') do |f|
19
+ f << <<-RUBY.gsub(/^\s+/, '')
20
+ file_cache_path "/tmp/chef-solo"
21
+ data_bag_path "/tmp/chef-solo/data_bags"
22
+ cookbook_path [ "/tmp/chef-solo/site-cookbooks",
23
+ "/tmp/chef-solo/cookbooks" ]
24
+ RUBY
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ # Taken from Vagrant's patch: https://gist.github.com/867960
2
+ # based on Brian Akins's patch: http://lists.opscode.com/sympa/arc/chef/2011-02/msg00000.html
3
+ if Chef::Config[:solo]
4
+ class Chef
5
+ module Mixin
6
+ module Language
7
+ def data_bag(bag)
8
+ @solo_data_bags = {} if @solo_data_bags.nil?
9
+ unless @solo_data_bags[bag]
10
+ @solo_data_bags[bag] = {}
11
+ data_bag_path = Chef::Config[:data_bag_path]
12
+ Dir.glob(File.join(data_bag_path, bag, "*.json")).each do |f|
13
+ item = JSON.parse(IO.read(f))
14
+ @solo_data_bags[bag][item['id']] = item
15
+ end
16
+ end
17
+ @solo_data_bags[bag].keys
18
+ end
19
+
20
+ def data_bag_item(bag, item)
21
+ data_bag(bag) unless ( !@solo_data_bags.nil? && @solo_data_bags[bag])
22
+ @solo_data_bags[bag][item]
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ class Chef
30
+ class Recipe
31
+ def search(bag_name, query=nil)
32
+ Chef::Log.warn("Simplistic search patch, ignoring query of %s" % [query]) unless query.nil?
33
+ data_bag(bag_name.to_s).each do |bag_item_id|
34
+ bag_item = data_bag_item(bag_name.to_s, bag_item_id)
35
+ yield bag_item
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,117 @@
1
+ require 'chef/knife'
2
+ require 'knife-solo/ssh_command'
3
+ require 'knife-solo/kitchen_command'
4
+
5
+ class Chef
6
+ class Knife
7
+ # Approach ported from littlechef (https://github.com/tobami/littlechef)
8
+ # Copyright 2010, 2011, Miquel Torres <tobami@googlemail.com>
9
+ class Prepare < Knife
10
+ include KnifeSolo::SshCommand
11
+ include KnifeSolo::KitchenCommand
12
+
13
+ banner "knife prepare [user@]hostname (options)"
14
+
15
+ def run
16
+ super
17
+ send("#{distro[:type]}_gem_install")
18
+ generate_node_config
19
+ end
20
+
21
+ def generate_node_config
22
+ File.open(node_config, 'w') do |f|
23
+ f.print <<-JSON.gsub(/^\s+/, '')
24
+ { "run_list": [] }
25
+ JSON
26
+ end unless node_config.exist?
27
+ end
28
+
29
+ def package_list
30
+ @packages.join(' ')
31
+ end
32
+
33
+ # TODO (mat): integration test this, not a gem install
34
+ def emerge_gem_install
35
+ ui.msg("Installing required packages...")
36
+ run_command("sudo USE='-test' ACCEPT_KEYWORDS='~amd64' emerge -u chef")
37
+ end
38
+
39
+ def add_rpm_repos
40
+ ui.message("Adding EPEL and ELFF...")
41
+ repo_url = "http://download.fedora.redhat.com"
42
+ repo_path = "/pub/epel/5/i386/epel-release-5-4.noarch.rpm"
43
+ result = run_command("sudo rpm -Uvh #{repo_url}#{repo_path}")
44
+ installed = "package epel-release-5-4.noarch is already installed"
45
+ raise result.stderr unless result.success? || result.stdout.match(installed)
46
+
47
+ repo_url = "http://download.elff.bravenet.com"
48
+ repo_path = "/5/i386/elff-release-5-3.noarch.rpm"
49
+ result = run_command("sudo rpm -Uvh #{repo_url}#{repo_path}")
50
+ installed = "package elff-release-5-3.noarch is already installed"
51
+ raise result.stderr unless result.success? || result.stdout.match(installed)
52
+ end
53
+
54
+ # TODO (mat): integration test this, will probably break without sudo
55
+ def rpm_gem_install
56
+ ui.msg("Installing required packages...")
57
+ @packages = %w(ruby ruby-shadow gcc gcc-c++ ruby-devel wget rsync)
58
+ run_command("sudo yum -y install #{package_list}")
59
+ gem_install
60
+ end
61
+
62
+ def debian_gem_install
63
+ ui.msg "Updating apt caches..."
64
+ run_command("sudo apt-get update")
65
+
66
+ ui.msg "Installing required packages..."
67
+ @packages = %w(ruby ruby-dev libopenssl-ruby irb
68
+ build-essential wget ssl-cert rsync)
69
+ run_command <<-BASH
70
+ sudo DEBIAN_FRONTEND=noninteractive apt-get --yes install #{package_list}
71
+ BASH
72
+
73
+ gem_install
74
+ end
75
+
76
+ def gem_install
77
+ ui.msg "Installing rubygems from source..."
78
+ release = "rubygems-1.7.2"
79
+ file = "#{release}.tgz"
80
+ url = "http://production.cf.rubygems.org/rubygems/#{file}"
81
+ run_command("wget #{url}")
82
+ run_command("tar zxf #{file}")
83
+ run_command("cd #{release} && sudo ruby setup.rb --no-format-executable")
84
+ run_command("sudo rm -rf #{release} #{file}")
85
+ run_command("sudo gem install --no-rdoc --no-ri chef")
86
+ end
87
+
88
+ def issue
89
+ run_command("cat /etc/issue").stdout
90
+ end
91
+
92
+ def distro
93
+ @distro ||= case issue
94
+ when %r{Debian GNU/Linux 5}
95
+ {:type => "debian", :version => "lenny"}
96
+ when %r{Debian GNU/Linux 6}
97
+ {:type => "debian", :version => "squeeze"}
98
+ when %r{Debian GNU/Linux wheezy}
99
+ {:type => "debian", :version => "wheezy"}
100
+ when %r{Ubuntu}
101
+ version = run_command("lsb_release -cs").stdout.strip
102
+ {:type => "debian", :version => version}
103
+ when %r{CentOS}
104
+ {:type => "rpm", :version => "CentOS"}
105
+ when %r{Red Hat Enterprise Linux}
106
+ {:type => "rpm", :version => "Red Hat"}
107
+ when %r{Scientific Linux SL}
108
+ {:type => "rpm", :version => "Scientific Linux"}
109
+ when %r{This is \\n\.\\O (\\s \\m \\r) \\t}
110
+ {:type => "gentoo", :version => "Gentoo"}
111
+ else
112
+ raise "Contents of /etc/issue not recognized, please fork https://github.com/matschaffer/knife-solo and add support for your distro."
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1 @@
1
+ require 'knife-solo/info'
@@ -0,0 +1,3 @@
1
+ module KnifeSolo
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,18 @@
1
+ module KnifeSolo
2
+ module KitchenCommand
3
+ class OutOfKitchenError < StandardError
4
+ def message
5
+ "This command must be run inside a Chef solo kitchen."
6
+ end
7
+ end
8
+
9
+ def required_files
10
+ %w(nodes roles cookbooks data_bags solo.rb)
11
+ end
12
+
13
+ def run
14
+ all_present = required_files.inject(true) { |m, f| m && File.exists?(f) }
15
+ raise OutOfKitchenError.new unless all_present
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,161 @@
1
+ require 'pathname'
2
+
3
+ module KnifeSolo
4
+ module SshCommand
5
+ def self.included(other)
6
+ other.instance_eval do
7
+ deps do
8
+ require 'net/ssh'
9
+ end
10
+
11
+ option :ssh_config,
12
+ :short => "-F configfile",
13
+ :long => "--ssh-config-file configfile",
14
+ :description => "Alternate location for ssh config file"
15
+
16
+ option :ssh_password,
17
+ :short => "-P PASSWORD",
18
+ :long => "--ssh-password PASSWORD",
19
+ :description => "The ssh password"
20
+
21
+ option :ssh_identity,
22
+ :short => "-i FILE",
23
+ :long => "--ssh-identity FILE",
24
+ :description => "The ssh identity file"
25
+
26
+ option :ssh_port,
27
+ :short => "-p FILE",
28
+ :long => "--ssh-port FILE",
29
+ :description => "The ssh port"
30
+ end
31
+ end
32
+
33
+ def node_config
34
+ Pathname.new("nodes/#{host}.json")
35
+ end
36
+
37
+ def host_descriptor
38
+ return @host_descriptor if @host_descriptor
39
+ parts = @name_args.first.split('@')
40
+ @host_descriptor = {
41
+ :host => parts.pop,
42
+ :user => parts.pop
43
+ }
44
+ end
45
+
46
+ def user
47
+ host_descriptor[:user] || config_file_options[:user] || ENV['USER']
48
+ end
49
+
50
+ def host
51
+ host_descriptor[:host]
52
+ end
53
+
54
+ def ask_password
55
+ ui.ask("Enter the password for #{user}@#{host}: ") do |q|
56
+ q.echo = false
57
+ end
58
+ end
59
+
60
+ def password
61
+ config[:ssh_password] ||= ask_password
62
+ end
63
+
64
+ def try_connection
65
+ Net::SSH.start(host, user, connection_options) do |ssh|
66
+ ssh.exec!("true")
67
+ end
68
+ end
69
+
70
+ def config_file_options
71
+ Net::SSH::Config.for(host, config_files)
72
+ end
73
+
74
+ def connection_options
75
+ options = config_file_options
76
+ options[:port] = config[:ssh_port] if config[:ssh_port]
77
+ options[:password] = config[:ssh_password] if config[:ssh_password]
78
+ options
79
+ end
80
+
81
+ def config_files
82
+ Array(config[:ssh_config] || Net::SSH::Config.default_files)
83
+ end
84
+
85
+ def detect_authentication_method
86
+ return @detected if @detected
87
+ begin
88
+ try_connection
89
+ rescue Errno::ETIMEDOUT
90
+ raise "Unable to connect to #{host}"
91
+ rescue Net::SSH::AuthenticationFailed
92
+ # Ensure the password is set or ask for it immediately
93
+ password
94
+ end
95
+ @detected = true
96
+ end
97
+
98
+ def ssh_args
99
+ host_arg = [user, host].compact.join('@')
100
+ config_arg = "-F #{config[:ssh_config]}" if config[:ssh_config]
101
+ ident_arg = "-i #{config[:ssh_identity]}" if config[:ssh_identity]
102
+ port_arg = "-p #{config[:ssh_port]}" if config[:ssh_port]
103
+
104
+ [host_arg, config_arg, ident_arg, port_arg].compact.join(' ')
105
+ end
106
+
107
+ class ExecResult
108
+ attr_accessor :stdout, :stderr, :exit_code
109
+
110
+ def initialize
111
+ @stdout = ""
112
+ @stderr = ""
113
+ end
114
+
115
+ def success?
116
+ exit_code == 0
117
+ end
118
+ end
119
+
120
+ def stream_command(command)
121
+ run_command(command, :streaming => true)
122
+ end
123
+
124
+ def run_command(command, options={})
125
+ detect_authentication_method
126
+
127
+ result = ExecResult.new
128
+ command = command.sub(/^\s*sudo/, 'sudo -p \'knife sudo password: \'')
129
+ Net::SSH.start(host, user, connection_options) do |ssh|
130
+ ssh.open_channel do |channel|
131
+ channel.request_pty
132
+ channel.exec(command) do |ch, success|
133
+ raise "ssh.channel.exec failure" unless success
134
+
135
+ channel.on_data do |ch, data| # stdout
136
+ if data =~ /^knife sudo password: /
137
+ ch.send_data("#{password}\n")
138
+ else
139
+ ui.stdout << data if options[:streaming]
140
+ result.stdout << data
141
+ end
142
+ end
143
+
144
+ channel.on_extended_data do |ch, type, data|
145
+ next unless type == 1
146
+ ui.stderr << data if options[:streaming]
147
+ result.stderr << data
148
+ end
149
+
150
+ channel.on_request("exit-status") do |ch, data|
151
+ result.exit_code = data.read_long
152
+ end
153
+
154
+ end
155
+ ssh.loop
156
+ end
157
+ end
158
+ result
159
+ end
160
+ end
161
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-solo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mat Schaffer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-01 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &70346489903680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70346489903680
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ requirement: &70346489903000 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70346489903000
36
+ - !ruby/object:Gem::Dependency
37
+ name: virtualbox
38
+ requirement: &70346489902460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70346489902460
47
+ - !ruby/object:Gem::Dependency
48
+ name: chef
49
+ requirement: &70346489901760 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70346489901760
58
+ - !ruby/object:Gem::Dependency
59
+ name: net-ssh
60
+ requirement: &70346489901160 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 2.1.3
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70346489901160
69
+ description: Handles bootstrapping, running chef solo, rsyncing cookbooks etc
70
+ email: mat@schaffer.me
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/chef/knife/cook.rb
76
+ - lib/chef/knife/kitchen.rb
77
+ - lib/chef/knife/patches/data_bags_patch.rb
78
+ - lib/chef/knife/prepare.rb
79
+ - lib/knife-solo/info.rb
80
+ - lib/knife-solo/kitchen_command.rb
81
+ - lib/knife-solo/ssh_command.rb
82
+ - lib/knife-solo.rb
83
+ homepage: https://github.com/matschaffer/knife-solo
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ segments:
96
+ - 0
97
+ hash: -2883154116349428333
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ segments:
105
+ - 0
106
+ hash: -2883154116349428333
107
+ requirements: []
108
+ rubyforge_project: nowarning
109
+ rubygems_version: 1.8.6
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: A collection of knife plugins for dealing with chef solo
113
+ test_files: []