capistrano-paratrooper-chef 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +131 -0
- data/Rakefile +2 -0
- data/capistrano-paratrooper-chef.gemspec +19 -0
- data/lib/capistrano-paratrooper-chef.rb +206 -0
- data/lib/capistrano-paratrooper-chef/install.rb +41 -0
- data/lib/capistrano-paratrooper-chef/version.rb +7 -0
- metadata +70 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Takeshi KOMIYA
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Paratrooper-chef
|
2
|
+
|
3
|
+
A capistrano recipe to execute chef-solo in each server.
|
4
|
+
All of you can use chef-solo remotely without chef-server.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'capistrano-paratrooper-chef'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install capistrano-paratrooper-chef
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
This recipe will execute chef-solo through paratrooper:chef task.
|
23
|
+
|
24
|
+
To setup paratrooper-chef for your application, add following in you config/deploy.rb.
|
25
|
+
|
26
|
+
# in "config/deploy.rb"
|
27
|
+
require 'capistrano-paratrooper-chef'
|
28
|
+
|
29
|
+
And then, put your chef-kitchen files to config/ directory.
|
30
|
+
by default, paratrooper-chef uses following files and directories.
|
31
|
+
|
32
|
+
* config/solo.rb
|
33
|
+
* config/cookbooks
|
34
|
+
* config/site-cookbooks
|
35
|
+
* config/roles
|
36
|
+
* config/data_bags
|
37
|
+
|
38
|
+
Finally, run capistrano with paratrooper:chef task. Then chef-solo runs at remote host.
|
39
|
+
|
40
|
+
$ cap paratrooper:chef
|
41
|
+
|
42
|
+
|
43
|
+
## Setup chef-solo to remote hosts
|
44
|
+
|
45
|
+
Paratrooper-chef includes another task to setup chef-solo to remote hosts.
|
46
|
+
To enable it, add following in your config/deploy.rb.
|
47
|
+
|
48
|
+
# in "config/deploy.rb"
|
49
|
+
require "capistrano-paratrooper-chef/install"
|
50
|
+
|
51
|
+
This recipe will install chef-solo during deploy:setup task.
|
52
|
+
|
53
|
+
## Define attributes for specific host
|
54
|
+
|
55
|
+
Paratrooper-chef supports switching attributes for each host.
|
56
|
+
Put definition to config/nodes/#{hostname}.json.
|
57
|
+
|
58
|
+
If there are no defitions for host, paratrooper-chef uses config/solo.rb as attributes.
|
59
|
+
|
60
|
+
## Chef roles Auto discovery
|
61
|
+
|
62
|
+
Chef roles auto discovery appends roles of chef to run_list of each host.
|
63
|
+
To enable auto discovery, set :chef_roles_auto_discovery true (as defualt, it is disabled).
|
64
|
+
|
65
|
+
# in "config/deploy.rb"
|
66
|
+
set :chef_roles_auto_discovery, true
|
67
|
+
|
68
|
+
This feature makes name-based relations with role of capistrano and chef's one::
|
69
|
+
* Discovering role definitions of chef from role-names of capistrano that server is assigned
|
70
|
+
* Run chef with discovered roles at each server
|
71
|
+
* Be able to play different roles of chef for each server
|
72
|
+
|
73
|
+
|
74
|
+
For example, 'web.example.com' plays :web role:
|
75
|
+
|
76
|
+
set :web, 'web.example.com'
|
77
|
+
|
78
|
+
And, there is role defition named 'web.json'.
|
79
|
+
|
80
|
+
# config/roles/web.json
|
81
|
+
{
|
82
|
+
"nginx" : {
|
83
|
+
# ...
|
84
|
+
},
|
85
|
+
"run_list" : [
|
86
|
+
"recipe[nginx]",
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
Then, paratrooper-chef detects automatically these relation, and append 'role[web]' to run_list of web.example.com .
|
91
|
+
(do not effect to other hosts)
|
92
|
+
|
93
|
+
## Options
|
94
|
+
|
95
|
+
Following options are available.
|
96
|
+
|
97
|
+
* Settings for remote host
|
98
|
+
|
99
|
+
* `:chef_solo_path` - the path of `chef-solo` command. use `chef-solo` by default (search command from $PATH).
|
100
|
+
* `:chef_working_dir` - the path where chef-kitchen should installed. use `$HOME/chef-solo` by default.
|
101
|
+
* `:chef_cache_dir` - the path for caches. use `/var/chef/cache` by default.
|
102
|
+
|
103
|
+
* Settings for chef and paratrooper
|
104
|
+
|
105
|
+
* `:chef_roles_auto_discovery` - Enable "Chef roles Auto discovery". use `false` by default.
|
106
|
+
* `:chef_verbose_logging`, - Enable verbose logging mode of `chef-solo`. use `true` by default.
|
107
|
+
* `:chef_debug` - Enable debug mode of `chef-solo`. use `false` by default.
|
108
|
+
|
109
|
+
* Settings for directories
|
110
|
+
|
111
|
+
* `:chef_kitchen_path` - root directory of kitchen. use `config` by default.
|
112
|
+
* `:chef_default_solo_json_path` - default attribute file a.k.a solo.json. use `solo.json` by default.
|
113
|
+
* `:chef_cookbooks_path` - cookbooks directories list. use `["cookbooks", "site-cookbooks"]` by default.
|
114
|
+
* `:chef_nodes_path` - nodes directory. use `nodes` by default.
|
115
|
+
* `:chef_roles_path` - roles directory. use `roles` by default.
|
116
|
+
* `:chef_databagas_path` - data bags directory. use `data_bagas` by default.
|
117
|
+
|
118
|
+
## Support recipes
|
119
|
+
|
120
|
+
Following recipes work fine with paratrooper-chef.
|
121
|
+
|
122
|
+
* rvm-capistrano (https://github.com/wayneeseguin/rvm-capistrano)
|
123
|
+
* capistrano-rbenv (https://github.com/yyuu/capistrano-rbenv)
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
1. Fork it
|
128
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
129
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
130
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
131
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/capistrano-paratrooper-chef/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Takeshi KOMIYA"]
|
6
|
+
gem.email = ["i.tkomiya@gmail.com"]
|
7
|
+
gem.description = %q{A capistrano task to invoke chef-solo}
|
8
|
+
gem.summary = %q{A capistrano task to invoke chef-solo}
|
9
|
+
gem.homepage = "https://github.com/tk0miya/capistrano-paratrooper-chef"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "capistrano-paratrooper-chef"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Capistrano::Paratrooper::Chef::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency("capistrano")
|
19
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require "json"
|
2
|
+
require "tempfile"
|
3
|
+
require "capistrano-paratrooper-chef/version"
|
4
|
+
|
5
|
+
|
6
|
+
Capistrano::Configuration.instance.load do
|
7
|
+
namespace :paratrooper do
|
8
|
+
# directory structure of chef-kitchen
|
9
|
+
set :chef_kitchen_path, "config"
|
10
|
+
set :chef_default_solo_json_path, "solo.json"
|
11
|
+
set :chef_cookbooks_path, ["cookbooks", "site-cookbooks"]
|
12
|
+
set :chef_nodes_path, "nodes"
|
13
|
+
set :chef_roles_path, "roles"
|
14
|
+
set :chef_databags_path, "data_bags"
|
15
|
+
|
16
|
+
# remote chef settings
|
17
|
+
set :chef_solo_path, "chef-solo"
|
18
|
+
set :chef_working_dir, "chef-solo"
|
19
|
+
set :chef_cache_dir, "/var/chef/cache"
|
20
|
+
|
21
|
+
# chef settings
|
22
|
+
set :chef_roles_auto_discovery, false
|
23
|
+
set :chef_verbose_logging, true
|
24
|
+
set :chef_debug, false
|
25
|
+
|
26
|
+
def sudocmd
|
27
|
+
envvars = fetch(:default_environment, {}).collect{|k, v| "#{k}=#{v}"}
|
28
|
+
|
29
|
+
begin
|
30
|
+
old_sudo = self[:sudo]
|
31
|
+
if fetch(:rvm_type, nil) == :user
|
32
|
+
self[:sudo] = "rvmsudo_secure_path=1 #{File.join(rvm_bin_path, "rvmsudo")}"
|
33
|
+
end
|
34
|
+
|
35
|
+
if envvars
|
36
|
+
cmd = "#{top.sudo} env #{envvars.join(" ")}"
|
37
|
+
else
|
38
|
+
cmd = top.sudo
|
39
|
+
end
|
40
|
+
ensure
|
41
|
+
self[:sudo] = old_sudo if old_sudo
|
42
|
+
end
|
43
|
+
|
44
|
+
cmd
|
45
|
+
end
|
46
|
+
|
47
|
+
def sudo(command, *args)
|
48
|
+
run "#{sudocmd} #{command}", *args
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_path(*path)
|
52
|
+
File.join(fetch(:chef_working_dir), *path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def cookbooks_paths
|
56
|
+
fetch(:chef_cookbooks_path).collect{|path| File.join(fetch(:chef_kitchen_path), path)}
|
57
|
+
end
|
58
|
+
|
59
|
+
def roles_path
|
60
|
+
File.join(fetch(:chef_kitchen_path), fetch(:chef_roles_path))
|
61
|
+
end
|
62
|
+
|
63
|
+
def role_exists?(name)
|
64
|
+
File.exist?(File.join(roles_path, name.to_s + ".json")) ||
|
65
|
+
File.exist?(File.join(roles_path, name.to_s + ".rb"))
|
66
|
+
end
|
67
|
+
|
68
|
+
def databags_path
|
69
|
+
File.join(fetch(:chef_kitchen_path), fetch(:chef_databags_path))
|
70
|
+
end
|
71
|
+
|
72
|
+
def nodes_path
|
73
|
+
File.join(fetch(:chef_kitchen_path), fetch(:chef_nodes_path))
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
namespace :run_list do
|
78
|
+
def solo_json_path_for(name)
|
79
|
+
path = File.join(nodes_path, name.to_s + ".json")
|
80
|
+
if File.exist?(path)
|
81
|
+
path
|
82
|
+
else
|
83
|
+
File.join(fetch(:chef_kitchen_path), fetch(:chef_default_solo_json_path))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def discover
|
88
|
+
find_servers_for_task(current_task).each do |server|
|
89
|
+
begin
|
90
|
+
open(solo_json_path_for(server.host)) do |fd|
|
91
|
+
server.options[:chef_attributes] = JSON.load(fd)
|
92
|
+
|
93
|
+
if server.options[:chef_attributes]["run_list"].nil?
|
94
|
+
server.options[:chef_attributes]["run_list"] = []
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
server.options[:chef_attributes] = attrs = {"run_list" => []}
|
99
|
+
end
|
100
|
+
|
101
|
+
if fetch(:chef_roles_auto_discovery)
|
102
|
+
role_names_for_host(server).each do |role|
|
103
|
+
server.options[:chef_attributes]["run_list"] << "role[#{role}]" if role_exists?(role)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def discovered_attributes
|
110
|
+
find_servers_for_task(current_task).collect{|server| server.options[:chef_attributes]}.compact
|
111
|
+
end
|
112
|
+
|
113
|
+
def discovered_lists
|
114
|
+
discovered_attributes.collect{|attr| attr["run_list"]}
|
115
|
+
end
|
116
|
+
|
117
|
+
def unique?
|
118
|
+
if fetch(:chef_roles_auto_discovery)
|
119
|
+
discovered_lists.uniq.size == 1
|
120
|
+
else
|
121
|
+
true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def ensure
|
126
|
+
if discovered_lists.all?{|run_list| run_list.empty?}
|
127
|
+
abort "You must specify at least one recipe or role"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
namespace :chef do
|
133
|
+
task :default, :except => { :no_release => true } do
|
134
|
+
run_list.discover
|
135
|
+
run_list.ensure
|
136
|
+
kitchen.ensure_cookbooks
|
137
|
+
kitchen.ensure_working_dir
|
138
|
+
kitchen.upload
|
139
|
+
chef.generate_solo_rb
|
140
|
+
chef.generate_solo_json
|
141
|
+
chef.execute
|
142
|
+
end
|
143
|
+
|
144
|
+
task :solo, :except => { :no_release => true } do
|
145
|
+
chef.default
|
146
|
+
end
|
147
|
+
|
148
|
+
def generate_solo_rb
|
149
|
+
config = <<-CONF
|
150
|
+
root = File.expand_path(File.dirname(__FILE__))
|
151
|
+
file_cache_path #{fetch(:chef_cache_dir).inspect}
|
152
|
+
cookbook_path #{kitchen.cookbooks_paths.inspect}.collect{|dir| File.join(root, dir)}
|
153
|
+
role_path File.join(root, #{kitchen.roles_path.inspect})
|
154
|
+
data_bag_path File.join(root, #{kitchen.databags_path.inspect})
|
155
|
+
verbose_logging #{fetch(:chef_verbose_logging)}
|
156
|
+
CONF
|
157
|
+
put config, remote_path("solo.rb"), :via => :scp
|
158
|
+
end
|
159
|
+
|
160
|
+
def generate_solo_json
|
161
|
+
find_servers_for_task(current_task).each do |server|
|
162
|
+
put server.options[:chef_attributes].to_json, remote_path("solo.json"), :hosts => server.host, :via => :scp
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
desc "Run chef-solo"
|
167
|
+
task :execute, :except => { :no_release => true } do
|
168
|
+
logger.info "Now running chef-solo"
|
169
|
+
command = "#{chef_solo_path} -c #{remote_path("solo.rb")} -j #{remote_path("solo.json")}#{' -l debug' if fetch(:chef_debug)}"
|
170
|
+
if run_list.unique?
|
171
|
+
sudo command
|
172
|
+
else
|
173
|
+
parallel do |session|
|
174
|
+
session.when "options[:chef_attributes]['run_list'].size > 0",
|
175
|
+
"#{sudocmd} #{command}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
namespace :kitchen do
|
182
|
+
def ensure_cookbooks
|
183
|
+
abort "No cookbooks found in #{fetch(:cookbooks_directory).inspect}" if kitchen.cookbooks_paths.empty?
|
184
|
+
end
|
185
|
+
|
186
|
+
def ensure_working_dir
|
187
|
+
run "rm -rf #{fetch(:chef_working_dir)} && mkdir -p #{fetch(:chef_working_dir)}"
|
188
|
+
sudo "mkdir -p #{fetch(:chef_cache_dir)}"
|
189
|
+
end
|
190
|
+
|
191
|
+
desc "Upload files in kitchen"
|
192
|
+
task :upload, :except => { :no_release => true } do
|
193
|
+
kitchen_paths = [cookbooks_paths, roles_path, databags_path].flatten.compact.select{|d| File.exists?(d)}
|
194
|
+
tarball = Tempfile.new("kitchen.tar")
|
195
|
+
begin
|
196
|
+
tarball.close
|
197
|
+
system "tar -czf #{tarball.path} #{kitchen_paths.join(' ')}"
|
198
|
+
top.upload tarball.path, remote_path("kitchen.tar"), :via => :scp
|
199
|
+
run "cd #{fetch(:chef_working_dir)} && tar -xzf kitchen.tar"
|
200
|
+
ensure
|
201
|
+
tarball.unlink
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'capistrano-paratrooper-chef'
|
2
|
+
|
3
|
+
Capistrano::Configuration.instance.load do
|
4
|
+
namespace :paratrooper do
|
5
|
+
namespace :chef do
|
6
|
+
set :chef_version, ">= 11.0.0"
|
7
|
+
|
8
|
+
on :load do
|
9
|
+
if top.namespaces.key?(:rbenv)
|
10
|
+
after "rbenv:setup", "paratrooper:chef:setup"
|
11
|
+
elsif top.namespaces.key?(:rvm)
|
12
|
+
after "rvm:install_ruby", "paratrooper:chef:setup"
|
13
|
+
else
|
14
|
+
after "deploy:setup" "paratrooper:chef:setup"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Installs chef"
|
19
|
+
task :setup, :except => { :no_release => true } do
|
20
|
+
required_version = fetch(:chef_version).inspect
|
21
|
+
installed = capture("gem list -i chef -v #{required_version} || true").strip
|
22
|
+
|
23
|
+
if installed == "false"
|
24
|
+
if fetch(:rvm_type, nil) == :user or fetch(:rbenv_path, nil)
|
25
|
+
run "gem uninstall -xaI chef || true"
|
26
|
+
run "gem install chef -v #{fetch(:chef_version).inspect} --quiet --no-ri --no-rdoc"
|
27
|
+
run "gem install ruby-shadow --quiet --no-ri --no-rdoc"
|
28
|
+
|
29
|
+
if fetch(:rbenv_path, nil)
|
30
|
+
rbenv.rehash
|
31
|
+
end
|
32
|
+
else
|
33
|
+
sudo "gem uninstall -xaI chef || true"
|
34
|
+
sudo "gem install chef -v #{fetch(:chef_version).inspect} --quiet --no-ri --no-rdoc"
|
35
|
+
sudo "gem install ruby-shadow --quiet --no-ri --no-rdoc"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-paratrooper-chef
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Takeshi KOMIYA
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capistrano
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: A capistrano task to invoke chef-solo
|
31
|
+
email:
|
32
|
+
- i.tkomiya@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- capistrano-paratrooper-chef.gemspec
|
43
|
+
- lib/capistrano-paratrooper-chef.rb
|
44
|
+
- lib/capistrano-paratrooper-chef/install.rb
|
45
|
+
- lib/capistrano-paratrooper-chef/version.rb
|
46
|
+
homepage: https://github.com/tk0miya/capistrano-paratrooper-chef
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.24
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A capistrano task to invoke chef-solo
|
70
|
+
test_files: []
|