knife-flow 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in knife-flow.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ knife-flow
2
+ ========
3
+ A collection of Chef plugins for managing the migration of cookbooks to environments in different Opscode organizations.
4
+ The main reason for having a workflow around the development and promotion of cookbooks is to ensure quality, reliability and administrative security of the process.
5
+
6
+ Requirements
7
+ ---------------
8
+ Right now knife-flow is build with many assumptions:
9
+
10
+ * The knife-flow assumes you have at least 2 orgs; one for "development" and one for "production".
11
+ * The "development" org has one environment called "candidate".
12
+ * The "production" org has an "innovate" and a "production" environment.
13
+ * You are using git flow [http://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/](http://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/) for your chef-repo project.
14
+
15
+ Installing knife-flow
16
+ -------------------
17
+ Be sure you are running the latest version Chef.
18
+
19
+ Map the "development" org to the knife.rb file, and map the "production" org to a knife-production.rb file.
20
+
21
+ Copy the increment.rb, promote.rb and release.rb files to the <tt>chef-repo/.chef/knife/plugins</tt> directory.
22
+
23
+ Plugins
24
+ ---------------
25
+
26
+ ### increment
27
+ Increments the cookbooks version by 1 digit at the patch level (i.e. 2.3.1 -> 2.3.2 ) <br />
28
+ Uploads the cookbook by running <tt> knife cookbook upload COOKBOOK COOKBOOK </tt> <br />
29
+ Commits the changes to the "develop" branch <br />
30
+
31
+
32
+ $ knife increment COOKBOOK COOKBOOK ...
33
+
34
+
35
+ This plugin is useful when working on the projects in the "sandbox" stage. The "_default" environment will always load the latest versions of the cookbooks.
36
+
37
+
38
+ ### promote
39
+ Increments the cookbooks version by 1 digit at the patch level ( i.e. 2.3.1 -> 2.3.2 ) <br />
40
+ Uploads the cookbook by running <tt> knife cookbook upload COOKBOOK COOKBOOK </tt> <br />
41
+ Updates the environments/ENVIRONMENT.json file with the list of COOKBOOK COOKBOOK and relative new versions. <br />
42
+ Uploads the ENVIRONMENT.json file to the "development" org. <br />
43
+ Commits the changes to the "develop" branch. <br />
44
+
45
+
46
+ $ knife promote ENVIRONMENT(i.e. candidate) COOKBOOK COOKBOOK ...
47
+
48
+
49
+ This plugin is useful when working on the projects in the "validation" and "performance" stage. The "candidate" environment will be used to validate the cookbooks versions.
50
+
51
+
52
+ ### release
53
+ Copies the "candidate" environment cookbook list and transfer them to the ENVIRONMENT in the "production" org. <br />
54
+ Commits all changes and creates a release tag TAG using the <tt> git flow release start/finish TAG </tt>. <br />
55
+ Uploads all cookbooks to the "production" org. <br />
56
+
57
+ $ knife release ENVIRONMENT(i.e. innovate or production) TAG(i.e. 2011.2.3)
58
+
59
+ This plugin is useful when we are ready to migrate the cookbooks to the environments in the "production" org.
60
+
61
+ License terms
62
+ -------------
63
+ Authors:: Johnlouis Petitbon, Jacob Zimmerman, Aaron Suggs
64
+
65
+ Copyright:: Copyright (c) 2009-2011 Medidata Solutions Worldwide, Inc.
66
+
67
+ License:: Apache License, Version 2.0
68
+
69
+
70
+ Licensed under the Apache License, Version 2.0 (the "License");
71
+ you may not use this file except in compliance with the License.
72
+ You may obtain a copy of the License at
73
+
74
+ http://www.apache.org/licenses/LICENSE-2.0
75
+
76
+ Unless required by applicable law or agreed to in writing, software
77
+ distributed under the License is distributed on an "AS IS" BASIS,
78
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
79
+ See the License for the specific language governing permissions and
80
+ limitations under the License.
81
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "knife-flow/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "knife-flow"
7
+ s.version = Knife::Flow::VERSION
8
+ s.authors = ["Johnlouis Petitbon", "Jacob Zimmerman", "Aaron Suggs"]
9
+ s.email = ["jpetitbon@mdsol.com"]
10
+ s.homepage = "https://github.com/mdsol/knife-flow"
11
+ s.summary = %q{A collection of Chef plugins for managing the migration of cookbooks to environments in different Opscode organizations.}
12
+ s.description = %q{The main reason for having a workflow around the development and promotion of cookbooks is to ensure quality, reliability and administrative security of the process.}
13
+
14
+ s.rubyforge_project = "knife-flow"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ ## Author:: Johnlouis Petitbon (<jpetitbon@mdsol.com>)
3
+ ##
4
+ ## Licensed under the Apache License, Version 2.0 (the "License");
5
+ ## you may not use this file except in compliance with the License.
6
+ ## You may obtain a copy of the License at
7
+ ##
8
+ ## http://www.apache.org/licenses/LICENSE-2.0
9
+ ##
10
+ ## Unless required by applicable law or agreed to in writing, software
11
+ ## distributed under the License is distributed on an "AS IS" BASIS,
12
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ ## See the License for the specific language governing permissions and
14
+ ## limitations under the License.
15
+ ##
16
+ #
17
+
18
+ require 'chef/knife'
19
+
20
+ module KnifeFlow
21
+ class Increment < Chef::Knife
22
+
23
+ deps do
24
+ require 'chef/cookbook_loader'
25
+ require 'chef/cookbook_uploader'
26
+ end
27
+
28
+ banner "knife increment COOKBOOK"
29
+
30
+ WORKING_BRANCH = "develop"
31
+
32
+ def run
33
+
34
+ @cookbooks = parse_name_args!
35
+
36
+ self.config = Chef::Config.merge!(config)
37
+
38
+ if !config[:cookbook_path]
39
+ raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to."
40
+ end
41
+
42
+ if check_branch(WORKING_BRANCH)
43
+
44
+ pull_branch(WORKING_BRANCH)
45
+
46
+ @cookbook_path = Array(config[:cookbook_path]).first
47
+
48
+ @cookbooks.each do | book |
49
+ metadata_file = File.join(@cookbook_path, book, "metadata.rb")
50
+
51
+ # 1) increase version on the metadata file
52
+ replace_version(find_version(book), increment_version(find_version(book)), metadata_file )
53
+
54
+ end
55
+
56
+ # 2) upload cookbooks to chef server
57
+ cookbook_up = Chef::Knife::CookbookUpload.new
58
+ cookbook_up.name_args = @cookbooks
59
+ cookbook_up.config[:freeze] = true
60
+ cookbook_up.run
61
+
62
+ # 3) commit and push WORKING_BRANCH
63
+ commit_and_push_branch(WORKING_BRANCH, "#{@cookbooks} have been incremented")
64
+
65
+ end
66
+
67
+ end
68
+
69
+ def parse_name_args!
70
+ if name_args.empty?
71
+ ui.error("USAGE: knife increment COOKBOOK COOKBOOK COOKBOOK")
72
+ exit 1
73
+ else
74
+ return name_args
75
+ end
76
+ end
77
+
78
+ def commit_and_push_branch(branch, comment)
79
+ print "--------------------------------- \n"
80
+ system("git pull origin #{branch}")
81
+ system("git add .")
82
+ system("git commit -am '#{comment}'")
83
+ system("git push origin #{branch}")
84
+ print "--------------------------------- \n"
85
+ end
86
+
87
+ def pull_branch(name)
88
+ print "--------------------------------- \n"
89
+ system("git pull origin #{name}")
90
+ print "--------------------------------- \n"
91
+ end
92
+
93
+ def check_branch(name)
94
+ if (`git status` =~ /#{name}/) != nil
95
+ return true
96
+ else
97
+ ui.error("USAGE: you must be in the #{name} branch")
98
+ exit 1
99
+ end
100
+ end
101
+
102
+ def find_version(name)
103
+ loader = Chef::CookbookLoader.new(@cookbook_path)
104
+ return loader[name].version
105
+ end
106
+
107
+ def increment_version(version)
108
+ current_version = version.split(".").map{|i| i.to_i}
109
+ current_version[2] = current_version[2] + 1
110
+ return current_version.join('.')
111
+ end
112
+
113
+ def replace_version(search_string, replace_string, file)
114
+ open_file = File.open(file, "r")
115
+ body_of_file = open_file.read
116
+ open_file.close
117
+ body_of_file.gsub!(search_string, replace_string)
118
+ File.open(file, "w") { |file| file << body_of_file }
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,156 @@
1
+ #
2
+ ## Author:: Johnlouis Petitbon (<jpetitbon@mdsol.com>)
3
+ ##
4
+ ## Licensed under the Apache License, Version 2.0 (the "License");
5
+ ## you may not use this file except in compliance with the License.
6
+ ## You may obtain a copy of the License at
7
+ ##
8
+ ## http://www.apache.org/licenses/LICENSE-2.0
9
+ ##
10
+ ## Unless required by applicable law or agreed to in writing, software
11
+ ## distributed under the License is distributed on an "AS IS" BASIS,
12
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ ## See the License for the specific language governing permissions and
14
+ ## limitations under the License.
15
+ ##
16
+ #
17
+
18
+ require 'chef/knife'
19
+
20
+ module KnifeFlow
21
+ class Promote < Chef::Knife
22
+
23
+ deps do
24
+ require 'chef/cookbook_loader'
25
+ require 'chef/cookbook_uploader'
26
+ require 'chef/environment'
27
+ require 'chef/knife/core/object_loader'
28
+ end
29
+
30
+ banner "knife promote ENVIRONMENT COOKBOOK"
31
+
32
+ WORKING_BRANCH = "develop"
33
+
34
+ def run
35
+
36
+
37
+ all_args = parse_name_args!
38
+ env_name = all_args[0]
39
+ all_args.shift
40
+ cookbooks = all_args
41
+
42
+ self.config = Chef::Config.merge!(config)
43
+
44
+ if !config[:cookbook_path]
45
+ raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to."
46
+ end
47
+ @cookbook_path = Array(config[:cookbook_path]).first
48
+
49
+
50
+ if check_branch(WORKING_BRANCH)
51
+
52
+ pull_branch(WORKING_BRANCH)
53
+
54
+ env_json = load_env_file(env_name)
55
+
56
+ env_data = JSON.parse(env_json)
57
+
58
+ cookbooks.each do | book |
59
+ metadata_file = File.join(@cookbook_path, book, "metadata.rb")
60
+
61
+ # 1) increase version on the metadata file
62
+ replace_version(find_version(book), increment_version(find_version(book)), metadata_file )
63
+
64
+ # 2) add or update the cookbook in the environment cookbook_versions list
65
+ env_data.cookbook_versions.merge!(book => find_version(book))
66
+
67
+ end
68
+
69
+ # 3) write the environment to file
70
+ File.open("environments/#{env_name}.json","w") do |f|
71
+ f.write(JSON.pretty_generate(env_data))
72
+ end
73
+
74
+ # 4) upload cookbooks to chef server
75
+ cookbook_up = Chef::Knife::CookbookUpload.new
76
+ cookbook_up.name_args = cookbooks
77
+ cookbook_up.config[:freeze] = true
78
+ cookbook_up.run
79
+
80
+
81
+ # 5) upload environment to chef server
82
+ knife_environment_from_file = Chef::Knife::EnvironmentFromFile.new
83
+ knife_environment_from_file.name_args = ["#{env_name}.json"]
84
+ output = knife_environment_from_file.run
85
+
86
+ # 6) commit and push all changes to develop
87
+ commit_and_push_branch(WORKING_BRANCH, "#{cookbooks.join(" and ").to_s} have been promoted to the #{env_name} environment")
88
+
89
+ end
90
+
91
+ end
92
+
93
+ def load_env_file(env_name)
94
+ if File.exist?("environments/#{env_name}.json")
95
+ File.read("environments/#{env_name}.json")
96
+ else
97
+ # TODO - we should handle the creation of the environment.json file if it doesn't exist.
98
+ raise ArgumentError, "environments/#{env_name}.json was not found; please create the environment file manually.#{env_name}"
99
+ end
100
+ end
101
+
102
+ def commit_and_push_branch(branch, comment)
103
+ print "--------------------------------- \n"
104
+ system("git pull origin #{branch}")
105
+ system("git add .")
106
+ system("git commit -am '#{comment}'")
107
+ system("git push origin #{branch}")
108
+ print "--------------------------------- \n"
109
+ end
110
+
111
+ def pull_branch(name)
112
+ print "--------------------------------- \n"
113
+ system("git pull origin #{name}")
114
+ print "--------------------------------- \n"
115
+ end
116
+
117
+ def check_branch(name)
118
+ if (`git status` =~ /#{name}/) != nil
119
+ return true
120
+ else
121
+ ui.error("USAGE: you must be in the #{name} branch.")
122
+ exit 1
123
+ end
124
+ end
125
+
126
+ def parse_name_args!
127
+ if name_args.empty?
128
+ ui.error("USAGE: knife promote ENVIRONMENT COOKBOOK COOKBOOK ...")
129
+ exit 1
130
+ else
131
+ return name_args
132
+ end
133
+ end
134
+
135
+ def find_version(name)
136
+ loader = Chef::CookbookLoader.new(@cookbook_path)
137
+ return loader[name].version
138
+ end
139
+
140
+ def increment_version(version)
141
+ current_version = version.split(".").map{|i| i.to_i}
142
+ current_version[2] = current_version[2] + 1
143
+ return current_version.join('.')
144
+ end
145
+
146
+ def replace_version(search_string, replace_string, file)
147
+ open_file = File.open(file, "r")
148
+ body_of_file = open_file.read
149
+ open_file.close
150
+ body_of_file.gsub!(search_string, replace_string)
151
+ File.open(file, "w") { |file| file << body_of_file }
152
+ end
153
+
154
+
155
+ end
156
+ end
@@ -0,0 +1,195 @@
1
+ #
2
+ ## Author:: Johnlouis Petitbon (<jpetitbon@mdsol.com>)
3
+ ##
4
+ ## Licensed under the Apache License, Version 2.0 (the "License");
5
+ ## you may not use this file except in compliance with the License.
6
+ ## You may obtain a copy of the License at
7
+ ##
8
+ ## http://www.apache.org/licenses/LICENSE-2.0
9
+ ##
10
+ ## Unless required by applicable law or agreed to in writing, software
11
+ ## distributed under the License is distributed on an "AS IS" BASIS,
12
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ ## See the License for the specific language governing permissions and
14
+ ## limitations under the License.
15
+ ##
16
+ #
17
+
18
+ require 'chef/knife'
19
+
20
+ module KnifeFlow
21
+ class Release < Chef::Knife
22
+
23
+ deps do
24
+ require 'chef/cookbook_loader'
25
+ require 'chef/cookbook_uploader'
26
+ require 'chef/environment'
27
+ require 'chef/knife/core/object_loader'
28
+ end
29
+
30
+ banner "knife release ENVIRONMENT TAG"
31
+
32
+ WORKING_BRANCH = "develop"
33
+ CANDIDATE_ENVIRONMENT = "candidate"
34
+
35
+ def run
36
+
37
+ all_args = parse_name_args!
38
+ env_name = all_args[0]
39
+ tag_name = all_args[1]
40
+
41
+ self.config = Chef::Config.merge!(config)
42
+
43
+ switch_org(env_name)
44
+
45
+ self.config = Chef::Config.merge!(config)
46
+
47
+ if !config[:cookbook_path]
48
+ raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to."
49
+ end
50
+ @cookbook_path = Array(config[:cookbook_path]).first
51
+
52
+ if check_branch(WORKING_BRANCH)
53
+
54
+ pull_branch(WORKING_BRANCH)
55
+
56
+ system("git fetch --tags")
57
+
58
+ # 1) start a new git-flow release
59
+ system("git flow release start #{tag_name}")
60
+
61
+ candidate_json = load_env_file(CANDIDATE_ENVIRONMENT)
62
+ candidate_data = JSON.parse(candidate_json)
63
+
64
+ env_json = load_env_file(env_name)
65
+ env_data = JSON.parse(env_json)
66
+
67
+ cb_a = []
68
+ candidate_data.cookbook_versions.each do | book_data |
69
+ cb_a << book_data[0]
70
+
71
+ # 2) add or update the cookbook in the environment cookbook_versions list
72
+ env_data.cookbook_versions.merge!(book_data[0] => book_data[1])
73
+
74
+ end
75
+
76
+ # 3) write the environment to file
77
+ File.open("environments/#{env_name}.json","w") do |f|
78
+ f.write(JSON.pretty_generate(env_data))
79
+ end
80
+
81
+ # 4) upload cookbooks to chef server
82
+ cookbook_up = Chef::Knife::CookbookUpload.new
83
+ cookbook_up.name_args = cb_a
84
+ cookbook_up.config[:freeze] = true
85
+ cookbook_up.run
86
+
87
+ # 5) upload environment to chef server
88
+ knife_environment_from_file = Chef::Knife::EnvironmentFromFile.new
89
+ knife_environment_from_file.name_args = ["#{env_name}.json"]
90
+ output = knife_environment_from_file.run
91
+
92
+ # 6) commit all changes and finish the git-flow release
93
+ system("git commit -am 'the candidate environemnt is now in production and tagged #{tag_name}'")
94
+ system("git flow release finish -m 'testing' #{tag_name}")
95
+
96
+ system("git push origin #{WORKING_BRANCH} --tags")
97
+
98
+ end
99
+
100
+ end
101
+
102
+ def switch_org(env_name)
103
+ # TODO - someone smarter than me can switch the organization without requiring 2 different knife.rb files
104
+ current_dir = File.dirname(__FILE__)
105
+ case env_name
106
+ when "innovate"
107
+ Chef::Config[:config_file] = "#{current_dir}/../../knife-production.rb"
108
+ when "production"
109
+ Chef::Config[:config_file] = "#{current_dir}/../../knife-production.rb"
110
+ when "candidate"
111
+ Chef::Config[:config_file] = "#{current_dir}/../../knife.rb"
112
+ end
113
+ ::File::open(config[:config_file]) { |f| apply_config(f.path) }
114
+ end
115
+
116
+ def load_env_file(env_name)
117
+ if File.exist?("environments/#{env_name}.json")
118
+ File.read("environments/#{env_name}.json")
119
+ else
120
+ # TODO - we should handle the creation of the environment.json file if it doesn't exist.
121
+ raise ArgumentError, "environments/#{env_name}.json was not found; please create the environment file manually.#{env_name}"
122
+ end
123
+ end
124
+
125
+ def apply_config(config_file_path)
126
+ Chef::Config.from_file(config_file_path)
127
+ Chef::Config.merge!(config)
128
+ end
129
+
130
+ def commit_and_push_branch(branch, comment)
131
+ print "--------------------------------- \n"
132
+ system("git pull origin #{branch}")
133
+ system("git add .")
134
+ system("git commit -am '#{comment}'")
135
+ system("git push origin #{branch}")
136
+ print "--------------------------------- \n"
137
+ end
138
+
139
+ def checkout_branch(name)
140
+ print "--------------------------------- \n"
141
+ system("git checkout #{name}")
142
+ print "--------------------------------- \n"
143
+ end
144
+
145
+ def checkout_tag(name)
146
+ print "--------------------------------- \n"
147
+ system("git checkout #{name}")
148
+ print "--------------------------------- \n"
149
+ end
150
+
151
+ def pull_branch(name)
152
+ print "--------------------------------- \n"
153
+ system("git pull origin #{name}")
154
+ print "--------------------------------- \n"
155
+ end
156
+
157
+ def check_branch(name)
158
+ if (`git status` =~ /#{name}/) != nil
159
+ return true
160
+ else
161
+ ui.error("USAGE: you must be in the #{name} branch.")
162
+ exit 1
163
+ end
164
+ end
165
+
166
+ def parse_name_args!
167
+ if name_args.empty?
168
+ ui.error("USAGE: knife promote ENVIRONMENT COOKBOOK COOKBOOK ...")
169
+ exit 1
170
+ else
171
+ return name_args
172
+ end
173
+ end
174
+
175
+ def find_version(name)
176
+ loader = Chef::CookbookLoader.new(@cookbook_path)
177
+ return loader[name].version
178
+ end
179
+
180
+ def increment_version(version)
181
+ current_version = version.split(".").map{|i| i.to_i}
182
+ current_version[2] = current_version[2] + 1
183
+ return current_version.join('.')
184
+ end
185
+
186
+ def replace_version(search_string, replace_string, file)
187
+ open_file = File.open(file, "r")
188
+ body_of_file = open_file.read
189
+ open_file.close
190
+ body_of_file.gsub!(search_string, replace_string)
191
+ File.open(file, "w") { |file| file << body_of_file }
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,5 @@
1
+ module Knife
2
+ module Flow
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-flow
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Johnlouis Petitbon
14
+ - Jacob Zimmerman
15
+ - Aaron Suggs
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-07-09 00:00:00 -04:00
21
+ default_executable:
22
+ dependencies: []
23
+
24
+ description: The main reason for having a workflow around the development and promotion of cookbooks is to ensure quality, reliability and administrative security of the process.
25
+ email:
26
+ - jpetitbon@mdsol.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - knife-flow.gemspec
39
+ - lib/chef/knife/increment.rb
40
+ - lib/chef/knife/promote.rb
41
+ - lib/chef/knife/release.rb
42
+ - lib/knife-flow/version.rb
43
+ has_rdoc: true
44
+ homepage: https://github.com/mdsol/knife-flow
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
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
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project: knife-flow
73
+ rubygems_version: 1.5.3
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A collection of Chef plugins for managing the migration of cookbooks to environments in different Opscode organizations.
77
+ test_files: []
78
+