knife-flow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+