berkflow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +6 -0
- data/LICENSE +13 -0
- data/README.md +66 -0
- data/Rakefile +1 -0
- data/berkflow.gemspec +29 -0
- data/bin/blo +5 -0
- data/lib/berkflow.rb +11 -0
- data/lib/berkflow/cli.rb +204 -0
- data/lib/berkflow/version.rb +3 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6f6efb7683cb8b8251dfc35923a520ecabc68273
|
4
|
+
data.tar.gz: 20ef5ac9d249a39a5bf1319d3eec4181fcbf72d5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f34faa9ed17ec3cef061cc435dffe70742e2b6ccb99fef0dd05d2513c62c62fb18d7e646d272c00a60f73c00ae9441f18090c7f02df5d43565680ab56c3945f8
|
7
|
+
data.tar.gz: 6714994388de16ebafb05eda462b72541b40ae0709f0fc60c3854762bc463bec0f6878ca4a542cc27ee7c84dbc63e1584bd01e26178f85f2ac0c176326d00871
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
berkflow_out/
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2014 Jamie Winsor
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Berkflow
|
2
|
+
|
3
|
+
A command line tool for managing Chef Environments using Berkshelf and the [Environment Cookbook Pattern](http://vialstudios.logdown.com/posts/166848-the-environment-cookbook-pattern).
|
4
|
+
|
5
|
+
> TLDR of the Environment Cookbook Pattern; You have one top level cookbook that is locked to a Chef Environment. One application per Chef Environment. This Chef Environment is named `{application_name}-{environment}` (i.e. "myface-dev").
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install berkflow
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
$ blo help
|
14
|
+
|
15
|
+
### Upgrading a Chef Environment
|
16
|
+
|
17
|
+
Berkflow exposes a command for configuring a Chef Environment and running Chef Client on all nodes in that environment.
|
18
|
+
|
19
|
+
$ blo upgrade myface-dev myface 1.2.3
|
20
|
+
Applying cookbook locks to myface-dev...
|
21
|
+
Discovering nodes in myface-dev...
|
22
|
+
Running Chef Client on 10 nodes...
|
23
|
+
Successfully ran Chef Client on 10 nodes
|
24
|
+
Done. See berkflow_out/20140331172904 for logs.
|
25
|
+
|
26
|
+
Your Chef Server must meet the following requirements:
|
27
|
+
|
28
|
+
* The `myface-dev` environment must exist
|
29
|
+
* Version 1.2.3 of the myface cookbook (and it's dependencies) must be uploaded to the server
|
30
|
+
* Version 1.2.3 of the myface cookbook must have a Berksfile.lock. A cookbook having a Berksfile.lock is said to be an "Environment Cookbook"
|
31
|
+
|
32
|
+
> Note: earlier versions of Berkshelf generated a chefignore file that included the Berksfile.lock. This will prevent your Berksfile.lock from being uploaded. Remove this line from the chefignore of your cookbook. This has been fixed in Berkshelf master and will ship with Berkshelf 3.0.
|
33
|
+
|
34
|
+
By default, the user you are logged into your current machine and your default id_rsa key will be used for SSH authentication. See the help menu for how to override SSH settings.
|
35
|
+
|
36
|
+
### Running Chef Client on a Chef Environment
|
37
|
+
|
38
|
+
Berkflow has you covered if you just want to run Chef Client on all the nodes in your Chef Environment.
|
39
|
+
|
40
|
+
$ blo run_chef myface-dev
|
41
|
+
Discovering nodes in myface-dev...
|
42
|
+
Running Chef Client on 10 nodes...
|
43
|
+
Successfully ran Chef Client on 10 nodes
|
44
|
+
Done. See berkflow_out/20140331180610 for logs.
|
45
|
+
|
46
|
+
### Running shell commands on a Chef Environment
|
47
|
+
|
48
|
+
Running arbitrary shell commands is possible, too!
|
49
|
+
|
50
|
+
$ blo exec myface-dev "ls -lah"
|
51
|
+
Discovering nodes in myface-dev...
|
52
|
+
Executing command on 10 nodes...
|
53
|
+
Successfully executed command on 10 nodes
|
54
|
+
Done. See berkflow_out/20140331180708 for logs.
|
55
|
+
|
56
|
+
Shell commands executed with `blo exec` are by default not run with sudo. Use the --sudo flag to elevate.
|
57
|
+
|
58
|
+
$ blo exec myface-dev "ls -lah" --sudo
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
1. Fork it ( https://github.com/[my-github-username]/berkflow/fork )
|
63
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
64
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
65
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
66
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/berkflow.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'berkflow/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "berkflow"
|
8
|
+
spec.version = Berkflow::VERSION
|
9
|
+
spec.authors = ["Jamie Winsor"]
|
10
|
+
spec.email = ["jamie@vialstudios.com"]
|
11
|
+
spec.summary = %q{A Cookbook-Centric Deployment workflow tool}
|
12
|
+
spec.description = %q{A CLI for managing Chef Environments using Berkshelf and the Environment Cookbook Pattern.}
|
13
|
+
spec.homepage = "https://github.com/reset/berkflow"
|
14
|
+
spec.license = "Apache 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "solve"
|
22
|
+
spec.add_dependency "berkshelf", "~> 3.0.0.beta7"
|
23
|
+
spec.add_dependency "ridley", "~> 2.5"
|
24
|
+
spec.add_dependency "ridley-connectors", "~> 1.7.0"
|
25
|
+
spec.add_dependency "thor", "~> 0.18"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
end
|
data/bin/blo
ADDED
data/lib/berkflow.rb
ADDED
data/lib/berkflow/cli.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'berkflow'
|
2
|
+
require 'thor'
|
3
|
+
require 'solve'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Berkflow
|
8
|
+
class Cli < Thor
|
9
|
+
def initialize(*args)
|
10
|
+
super(*args)
|
11
|
+
|
12
|
+
if @options[:verbose]
|
13
|
+
Ridley.logger.level = ::Logger::INFO
|
14
|
+
end
|
15
|
+
|
16
|
+
if @options[:debug]
|
17
|
+
Ridley.logger.level = ::Logger::DEBUG
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class_option :verbose,
|
22
|
+
type: :boolean,
|
23
|
+
desc: "Output verbose information",
|
24
|
+
aliases: "-v",
|
25
|
+
default: false
|
26
|
+
class_option :debug,
|
27
|
+
type: :boolean,
|
28
|
+
desc: "Output debug information",
|
29
|
+
aliases: "-d",
|
30
|
+
default: false
|
31
|
+
class_option :ssh_user,
|
32
|
+
type: :string,
|
33
|
+
desc: "SSH user to execute commands as",
|
34
|
+
aliases: "-u",
|
35
|
+
default: ENV["USER"]
|
36
|
+
class_option :ssh_password,
|
37
|
+
type: :string,
|
38
|
+
desc: "Perform SSH authentication with the given password",
|
39
|
+
aliases: "-p",
|
40
|
+
default: nil
|
41
|
+
class_option :ssh_key,
|
42
|
+
type: :string,
|
43
|
+
desc: "Perform SSH authentication with the given key",
|
44
|
+
aliases: "-P",
|
45
|
+
default: nil
|
46
|
+
|
47
|
+
method_option :sudo,
|
48
|
+
type: :boolean,
|
49
|
+
desc: "Execute with sudo",
|
50
|
+
default: false
|
51
|
+
desc "exec ENV CMD", "execute an arbitrary shell command on all nodes in an environment."
|
52
|
+
def exec(environment, command)
|
53
|
+
env = find_environment!(environment)
|
54
|
+
|
55
|
+
say "Discovering nodes in #{environment}..."
|
56
|
+
nodes = find_nodes(environment)
|
57
|
+
|
58
|
+
if nodes.empty?
|
59
|
+
say "No nodes in #{environment}. Done."
|
60
|
+
exit(0)
|
61
|
+
end
|
62
|
+
|
63
|
+
say "Executing command on #{nodes.length} nodes..."
|
64
|
+
success, failures, out = handle_results nodes.map { |node| ridley.node.run(node.public_hostname, command) }
|
65
|
+
|
66
|
+
unless success.empty?
|
67
|
+
say "Successfully executed command on #{success.length} nodes"
|
68
|
+
end
|
69
|
+
|
70
|
+
unless failures.empty?
|
71
|
+
error "Failed to execute command on #{failures.length} nodes"
|
72
|
+
end
|
73
|
+
|
74
|
+
say "Done. See #{out} for logs."
|
75
|
+
failures.empty? ? exit(0) : exit(1)
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "run_chef ENV", "run chef on all nodes in the given environment."
|
79
|
+
def run_chef(environment)
|
80
|
+
env = find_environment!(environment)
|
81
|
+
|
82
|
+
say "Discovering nodes in #{environment}..."
|
83
|
+
nodes = find_nodes(environment)
|
84
|
+
|
85
|
+
if nodes.empty?
|
86
|
+
say "No nodes in #{environment}. Done."
|
87
|
+
exit(0)
|
88
|
+
end
|
89
|
+
|
90
|
+
say "Running Chef Client on #{nodes.length} nodes..."
|
91
|
+
success, failures, out = handle_results nodes.map { |node| ridley.node.chef_run(node.public_hostname) }
|
92
|
+
|
93
|
+
unless success.empty?
|
94
|
+
say "Successfully ran Chef Client on #{success.length} nodes"
|
95
|
+
end
|
96
|
+
|
97
|
+
unless failures.empty?
|
98
|
+
error "Failed to run Chef Client on #{failures.length} nodes"
|
99
|
+
end
|
100
|
+
|
101
|
+
say "Done. See #{out} for logs."
|
102
|
+
failures.empty? ? exit(0) : exit(1)
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "upgrade ENV APP VERSION", "upgrade an environment to a specific application version."
|
106
|
+
def upgrade(environment, application, version)
|
107
|
+
version = sanitize_version(version)
|
108
|
+
env = find_environment!(environment)
|
109
|
+
cookbook = find_cookbook!(application, version)
|
110
|
+
|
111
|
+
file = Tempfile.new("berkflow")
|
112
|
+
unless contents = cookbook.download_file(:root_file, Berkshelf::Lockfile::DEFAULT_FILENAME, file.path)
|
113
|
+
error "#{application} (#{version}) did not contain a Berksfile.lock"
|
114
|
+
exit(1)
|
115
|
+
end
|
116
|
+
|
117
|
+
say "Applying cookbook locks to #{environment}..."
|
118
|
+
lockfile = Berkshelf::Lockfile.from_file(file.path)
|
119
|
+
unless lockfile.apply(environment)
|
120
|
+
error "Failed to apply Berksfile.lock to #{environment}."
|
121
|
+
exit(1)
|
122
|
+
end
|
123
|
+
|
124
|
+
run_chef(environment)
|
125
|
+
ensure
|
126
|
+
file.close(true) if file
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def ridley
|
132
|
+
@ridley ||= Ridley.new(server_url: config.chef.chef_server_url, client_name: config.chef.node_name,
|
133
|
+
client_key: config.chef.client_key, ssh: {
|
134
|
+
user: @options[:ssh_user], password: @options[:ssh_password], keys: @options[:ssh_key],
|
135
|
+
sudo: use_sudo?
|
136
|
+
})
|
137
|
+
end
|
138
|
+
|
139
|
+
def config
|
140
|
+
Berkshelf::Config.instance
|
141
|
+
end
|
142
|
+
|
143
|
+
def handle_results(result_set)
|
144
|
+
failure, success = result_set.partition { |result| result.error? }
|
145
|
+
log_dir = log_results(success, failure)
|
146
|
+
[success, failure, log_dir]
|
147
|
+
end
|
148
|
+
|
149
|
+
def log_results(success, failure)
|
150
|
+
out_dir = File.join("berkflow_out", Time.now.strftime("%Y%m%d%H%M%S"))
|
151
|
+
success_dir = File.join(out_dir, "success")
|
152
|
+
failure_dir = File.join(out_dir, "failure")
|
153
|
+
|
154
|
+
[success_dir, failure_dir].each { |dir| FileUtils.mkdir_p(dir) }
|
155
|
+
success.each { |result| write_logs(result, success_dir) }
|
156
|
+
failure.each { |result| write_logs(result, failure_dir) }
|
157
|
+
out_dir
|
158
|
+
end
|
159
|
+
|
160
|
+
def sanitize_version(version)
|
161
|
+
Solve::Version.new(version).to_s
|
162
|
+
rescue Solve::Errors::InvalidVersionFormat
|
163
|
+
error "Invalid version: #{version}. Provide a valid SemVer version string. (i.e. 1.2.3)."
|
164
|
+
exit(1)
|
165
|
+
end
|
166
|
+
|
167
|
+
def find_cookbook!(application, version)
|
168
|
+
unless cookbook = ridley.cookbook.find(application, version)
|
169
|
+
error "Cookbook not found: #{application} (#{version})."
|
170
|
+
exit(1)
|
171
|
+
end
|
172
|
+
cookbook
|
173
|
+
end
|
174
|
+
|
175
|
+
def find_environment!(environment)
|
176
|
+
unless env = ridley.environment.find(environment)
|
177
|
+
error "Environment not found: #{environment}"
|
178
|
+
exit(1)
|
179
|
+
end
|
180
|
+
env
|
181
|
+
end
|
182
|
+
|
183
|
+
def find_nodes(environment)
|
184
|
+
ridley.search(:node, "chef_environment:#{environment}")
|
185
|
+
end
|
186
|
+
|
187
|
+
def use_sudo?
|
188
|
+
@options[:sudo].nil? ? true : @options[:sudo]
|
189
|
+
end
|
190
|
+
|
191
|
+
def write_logs(result, dir)
|
192
|
+
write_stdout(result, dir)
|
193
|
+
write_stderr(result, dir)
|
194
|
+
end
|
195
|
+
|
196
|
+
def write_stdout(result, dir)
|
197
|
+
File.open(File.join(dir, "#{result.host}.stdout"), "w") { |file| file.write(result.stdout) }
|
198
|
+
end
|
199
|
+
|
200
|
+
def write_stderr(result, dir)
|
201
|
+
File.open(File.join(dir, "#{result.host}.stderr"), "w") { |file| file.write(result.stderr) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: berkflow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jamie Winsor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: solve
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: berkshelf
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.0.0.beta7
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0.beta7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ridley
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ridley-connectors
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.7.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.7.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.18'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.18'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: A CLI for managing Chef Environments using Berkshelf and the Environment
|
112
|
+
Cookbook Pattern.
|
113
|
+
email:
|
114
|
+
- jamie@vialstudios.com
|
115
|
+
executables:
|
116
|
+
- blo
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- Gemfile
|
122
|
+
- Gemfile.lock
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- berkflow.gemspec
|
127
|
+
- bin/blo
|
128
|
+
- lib/berkflow.rb
|
129
|
+
- lib/berkflow/cli.rb
|
130
|
+
- lib/berkflow/version.rb
|
131
|
+
homepage: https://github.com/reset/berkflow
|
132
|
+
licenses:
|
133
|
+
- Apache 2.0
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.0.14
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: A Cookbook-Centric Deployment workflow tool
|
155
|
+
test_files: []
|
156
|
+
has_rdoc:
|