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.
- data/lib/chef/knife/cook.rb +80 -0
- data/lib/chef/knife/kitchen.rb +29 -0
- data/lib/chef/knife/patches/data_bags_patch.rb +42 -0
- data/lib/chef/knife/prepare.rb +117 -0
- data/lib/knife-solo.rb +1 -0
- data/lib/knife-solo/info.rb +3 -0
- data/lib/knife-solo/kitchen_command.rb +18 -0
- data/lib/knife-solo/ssh_command.rb +161 -0
- metadata +113 -0
@@ -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
|
data/lib/knife-solo.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'knife-solo/info'
|
@@ -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: []
|