knife-table 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/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## v0.0.1
2
+ * Initial commit
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # ChefTable
2
+
3
+ ChefTable is a knife plugin to aid in cookbook development
4
+ workflow. Its intention is to help automate versioning
5
+ within environments and cookbook freezing based on a stable
6
+ branch. Building off of the knife-spork plugin ChefTable
7
+ helps to provide consistency within the environment.
8
+
9
+
10
+ ## Usage
11
+
12
+ Currently, two helpers are available:
13
+
14
+ `knife table set`
15
+
16
+ and
17
+
18
+ `knife table serve`
19
+
20
+ ### Setting the table
21
+
22
+ First, we set the table either by adding new or modifying
23
+ existing features. To do this, we set the table with a basic description
24
+ of what is being added:
25
+
26
+ `knife table set new feature`
27
+
28
+ This will create a new working branch named 'WIP-new_feature'. The prefix
29
+ for the branch defaults to 'WIP-' but can be modified using the `-p` option.
30
+ If it is known what cookbooks will be modified, you can provide them while
31
+ setting:
32
+
33
+ `knife table set -c iptables,mysql new feature`
34
+
35
+ ### Service
36
+
37
+ Service works on the assumption that any new code into the stable branch (master
38
+ by default) will arrive via pull requests. By default, it will find the last
39
+ pull request in the log and update based on changes within that merge. The default
40
+ behavior of the `serve` command will upload and freeze any changed cookbooks. Optionally,
41
+ environments can be provided to have the cookbook versions automatically pegged. Also,
42
+ roles and data bags can be automatically uploaded as well.
43
+
44
+ Options for serve:
45
+
46
+ * `--environments ENV[,ENV...]` 'Update versions in given environments'
47
+ * `--git-autopush` 'Automatically commit and push any changes to master'
48
+ * `--git-tag` 'Automatically create tag for frozen cookbook'
49
+ * `--git-branch BRANCH` 'Set working branch'
50
+ * `--git-remote-name NAME` 'Remote repo name'
51
+ * `--git-autocommit` 'Automatically commit changes'
52
+ * `--autoproceed` 'Answer yes to any prompts'
53
+ * `--roles` 'Upload any changed roles'
54
+ * `--data-bags` 'Upload any changed data bags'
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'knife-table'
3
+ s.version = '0.0.1'
4
+ s.summary = 'Help chef set and serve the table'
5
+ s.author = 'Chris Roberts'
6
+ s.email = 'chrisroberts.code@gmail.com'
7
+ s.homepage = 'http://github.com/heavywater/knife-table'
8
+ s.description = "Chef's table"
9
+ s.require_path = 'lib'
10
+ s.files = Dir.glob('**/*')
11
+ s.add_dependency 'knife-spork'
12
+ end
@@ -0,0 +1,54 @@
1
+ require 'pp'
2
+
3
+ class DataHolder
4
+
5
+ def initialize
6
+ @holder = {}
7
+ @path = nil
8
+ end
9
+
10
+ def method_missing(name, *args)
11
+ @holder[name] = args
12
+ end
13
+
14
+ def ==(holder)
15
+ raise TypeError.new('Comparisons only allowed between DataHolder instances')
16
+ self._holder == holder._holder
17
+ end
18
+
19
+ def _holder
20
+ @holder
21
+ end
22
+
23
+ def _load(path)
24
+ @path = path
25
+ self.instance_eval(
26
+ File.read(path)
27
+ )
28
+ self
29
+ end
30
+
31
+ def _output
32
+ output = ''
33
+ @holder.each_pair do |k,v|
34
+ output << "#{k}(\n"
35
+ inards = []
36
+ v.each do |item|
37
+ s = ''
38
+ PP.pp(item, s)
39
+ inards << s
40
+ end
41
+ output << inards.join(",\n")
42
+ output << ")\n"
43
+ end
44
+ output
45
+ File.open(@path, 'w') do |file|
46
+ file.write(output)
47
+ end
48
+ output
49
+ end
50
+
51
+ def _path
52
+ @path
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ require 'knife-table/helpers'
2
+
3
+ module KnifeTable
4
+ class TableClear < Chef::Knife
5
+
6
+ include KnifeTable::Helpers
7
+
8
+ deps do
9
+ require 'git'
10
+ require 'chef/knife/core/object_loader'
11
+ end
12
+
13
+ banner 'knife table clear'
14
+
15
+ def run
16
+
17
+ end
18
+
19
+ end
20
+ end
21
+
@@ -0,0 +1,332 @@
1
+ require 'knife-table/helpers'
2
+
3
+ module KnifeTable
4
+ class TableServe < Chef::Knife
5
+
6
+ include KnifeTable::Helpers
7
+
8
+ deps do
9
+ require 'git'
10
+ require 'chef/knife/core/object_loader'
11
+ end
12
+
13
+ banner "knife table serve [#PULLREQ|COMMITSHA[..COMMITSHA]]"
14
+
15
+ option :environments,
16
+ :short => '-e ENV[,ENV...]',
17
+ :long => '--environments ENV[,ENV...]',
18
+ :description => 'Update versions in given environments'
19
+
20
+ option :git_autopush,
21
+ :short => '-g',
22
+ :long => '--git-autopush',
23
+ :boolean => true,
24
+ :description => 'Automatically commit and push any changes to master'
25
+
26
+ option :git_tag,
27
+ :short => '-t',
28
+ :long => '--git-tag',
29
+ :boolean => true,
30
+ :description => 'Automatically create tag for frozen cookbook'
31
+
32
+ option :git_branch,
33
+ :short => '-b BRANCH',
34
+ :long => '--git-branch BRANCH',
35
+ :default => 'master',
36
+ :description => 'Set working branch'
37
+
38
+ option :git_remote_name,
39
+ :short => '-r NAME',
40
+ :long => '--git-remote-name NAME',
41
+ :default => 'origin',
42
+ :description => 'Remote repo name'
43
+
44
+ option :git_autocommit,
45
+ :short => '-c',
46
+ :long => '--git-autocommit',
47
+ :boolean => true,
48
+ :description => 'Automatically commit changes'
49
+
50
+ option :autoproceed,
51
+ :short => '-a',
52
+ :long => '--autoproceed',
53
+ :boolean => true,
54
+ :description => 'Answer yes to any prompts'
55
+
56
+ option :upload_roles,
57
+ :short => '-r',
58
+ :long => '--roles',
59
+ :boolean => true,
60
+ :description => 'Upload any changed roles'
61
+
62
+ option :upload_data_bags,
63
+ :short => '-d',
64
+ :long => '--data-bags',
65
+ :boolean => true,
66
+ :description => 'Upload any changed data bags'
67
+
68
+ def initialize(*args)
69
+ super
70
+ @environments = config[:environments].to_s.split(",").map(&:strip)
71
+ end
72
+
73
+ def run
74
+ sanity_checks
75
+ cookbooks = discover_changed(:cookbooks, *determine_commit_span).map{|c|c.split('/').first}
76
+ roles = discover_changed(:roles, *determine_commit_span) if config[:upload_roles]
77
+ data_bags = discover_changed(:data_bags, *determine_commit_span) if config[:upload_data_bags]
78
+
79
+ ui.msg ui.highline.color("#{' ' * 10}** Knife Table: Service started **", [HighLine::GREEN, HighLine::BOLD])
80
+ ui.highline.say ui.highline.color("Discovered cookbooks staged for freeze: #{cookbooks.join(', ')}", HighLine::CYAN)
81
+ ui.highline.say ui.highline.color("Environments staged to be updated: #{@environments.join(', ')}", HighLine::CYAN) unless @environments.empty?
82
+ ui.highline.say ui.highline.color("Roles staged to be uploaded: #{roles.sort.map{|r|r.sub(/\.(rb|json)/, '')}.join(', ')}", HighLine::CYAN) unless roles.nil? || roles.empty?
83
+ ui.highline.say ui.highline.color("Data Bags staged to be uploaded: #{data_bags.sort.join(', ')}", HighLine::CYAN) unless data_bags.nil? || data_bags.empty?
84
+
85
+ ui.highline.say "\n"
86
+
87
+ if(config[:autoproceed])
88
+ ui.warn "Autoproceeding based on config (ctrl+c to halt)"
89
+ sleep(3)
90
+ else
91
+ ui.confirm "Proceed"
92
+ end
93
+
94
+ ui.highline.say "\n"
95
+
96
+ ui.highline.say "#{ui.highline.color("Freezing cookbooks:", HighLine::GREEN)} "
97
+ cookbooks.each{|c| freeze_cookbook(c) }
98
+ ui.highline.say "\n"
99
+
100
+ unless(@environments.empty?)
101
+ ui.msg ui.highline.color("Updating environments:", HighLine::GREEN)
102
+ @environments.each do |env|
103
+ ui.highline.say " #{ui.highline.color(env, HighLine::BLUE)}: "
104
+ cookbooks.each{|c| update_environments(env, c) }
105
+ ui.highline.say "\n"
106
+ end
107
+
108
+ ui.highline.say "#{ui.highline.color("Uploading environments:", HighLine::GREEN)} "
109
+ upload_environments
110
+ ui.highline.say "\n"
111
+ end
112
+
113
+ upload_changes(:roles, roles) if roles && !roles.empty?
114
+ upload_changes(:data_bags, data_bags) if data_bags && !data_bags.empty?
115
+
116
+ if(config[:git_autocommit])
117
+ ui.highline.say "#{ui.highline.color("Committing environments:", HighLine::GREEN)} "
118
+ git_commit_environments(cookbooks)
119
+ ui.highline.say "\n"
120
+ end
121
+
122
+ if(config[:git_tag])
123
+ ui.highline.say "#{ui.highline.color("Tagging cookbooks:", HighLine::GREEN)} "
124
+ git_tag(cookbooks)
125
+ ui.highline.say "\n"
126
+ end
127
+
128
+ if(config[:git_autopush])
129
+ ui.highline.say "#{ui.highline.color("Pushing changes to remote repo:", HighLine::GREEN)} "
130
+ git_push
131
+ ui.highline.say "\n"
132
+ end
133
+
134
+ ui.msg ui.highline.color("#{' ' * 10}** Knife Table: Service complete **", [HighLine::GREEN, HighLine::BOLD])
135
+ end
136
+
137
+ def frozen_cookbook?(cookbook)
138
+ begin
139
+ spork_check = KnifeSpork::SporkCheck.new
140
+ spork_check.check_frozen(
141
+ cookbook,
142
+ spork_check.get_version(
143
+ cookbook_path,
144
+ cookbook
145
+ )
146
+ )
147
+ rescue Net::HTTPServerException => e
148
+ if(e.response.is_a?(Net::HTTPNotFound))
149
+ false
150
+ else
151
+ raise
152
+ end
153
+ end
154
+ end
155
+
156
+ def freeze_cookbook(cookbook)
157
+ unless(frozen_cookbook?(cookbook))
158
+ spork_upload = KnifeSpork::SporkUpload.new
159
+ spork_upload.config[:freeze] = true
160
+ spork_upload.config[:cookbook_path] = cookbook_path
161
+ repo_cookbook = spork_upload.cookbook_repo[cookbook]
162
+ repo_cookbook.freeze_version
163
+ Chef::CookbookUploader.new(repo_cookbook, cookbook_path).upload_cookbook
164
+ ui.highline.say "#{cookbook} "
165
+ else
166
+ ui.highline.say "#{ui.highline.color("#{cookbook} (already frozen)", HighLine::RED)} "
167
+ end
168
+ end
169
+
170
+ def update_environments(environment, cookbook)
171
+ promote_environment(environment, cookbook)
172
+ ui.highline.say "#{cookbook} "
173
+ end
174
+
175
+ def upload_environments
176
+ @environments.each do |environment|
177
+ spork_promote.config[:cookbook_path] = [cookbook_path]
178
+ spork_promote.save_environment_changes_remote(environment)
179
+ ui.highline.say "#{environment} "
180
+ end
181
+ end
182
+
183
+ def git_commit_environments(cookbooks)
184
+ commit = false
185
+ @environments.each do |environment|
186
+ status = git.status.changed[::File.join('environments', "#{environment}.json")]
187
+ if(status && !git.diff('HEAD', status.path).none?)
188
+ git.add(status.path)
189
+ commit_message = "Environment #{environment} cookbook version updates\n"
190
+ cookbooks.sort.each do |cookbook|
191
+ commit_message << "\n#{cookbook}: #{spork_promote.get_version(cookbook_path, cookbook)}"
192
+ end
193
+ git.commit(commit_message)
194
+ ui.highline.say "#{environment} "
195
+ @git_changed = true
196
+ commit = true
197
+ end
198
+ end
199
+ ui.highline.say "nothing to commit " unless commit
200
+ end
201
+
202
+ def git_tag(cookbooks)
203
+ cookbooks.each do |cookbook|
204
+ tag_string = "#{cookbook}-v#{spork_promote.get_version(cookbook_path, cookbook)}"
205
+ unless(git.tags.map(&:name).include?(tag_string))
206
+ git.add_tag(tag_string)
207
+ ui.highline.say "#{tag_string} "
208
+ @git_changed = true
209
+ else
210
+ ui.highline.say "#{ui.highline.color("#{tag_string} (exists)", HighLine::RED)} "
211
+ end
212
+ end
213
+ end
214
+
215
+ def git_push
216
+ if(@git_changed)
217
+ git.push(config[:git_remote_name], config[:git_branch], true)
218
+ ui.highline.say "pushed #{config[:git_branch]} to #{config[:git_remote_name]} "
219
+ else
220
+ ui.highline.say "nothing to push "
221
+ end
222
+ end
223
+
224
+ def upload_roles(role)
225
+ role_load = loader(:roles).load_from('roles', role)
226
+ role_load.save
227
+ end
228
+
229
+ def upload_data_bags(bag)
230
+ data_bag_load = loader(:data_bags).load_from('data_bags', bag.split.first, bag.split.last)
231
+ data_bag_load.save
232
+ end
233
+
234
+ private
235
+
236
+ def promote_environment(environment, cookbook)
237
+ version = spork_promote.get_version(cookbook_path, cookbook)
238
+ env = spork_promote.update_version_constraints(
239
+ Chef::Environment.load(environment),
240
+ cookbook,
241
+ version
242
+ )
243
+ env_json = spork_promote.pretty_print(env)
244
+ spork_promote.save_environment_changes(environment, env_json)
245
+ end
246
+
247
+ def spork_promote
248
+ unless(@spork_promote)
249
+ @spork_promote = KnifeSpork::SporkPromote.new
250
+ @spork_promote.config[:cookbook_path] = [cookbook_path]
251
+ @spork_promote.loader # work around for bad namespacing
252
+ @spork_promote.instance_variable_set(:@conf, AppConf.new) # configuration isn't isolated to single call so just stub
253
+ end
254
+ @spork_promote
255
+ end
256
+
257
+ def sanity_checks
258
+ unless(git.branches.map(&:name).include?(config[:git_branch]))
259
+ raise "Requested git branch (#{config[:git_branch]}) does not exist"
260
+ else
261
+ ui.warn "Checking out requested working branch: #{config[:git_branch]}"
262
+ @git.checkout(config[:git_branch]) if config[:git_branch]
263
+ end
264
+ unless(git.remotes.map(&:name).include?(config[:git_remote_name]))
265
+ raise "Specified remote #{config[:git_remote_name]} not found"
266
+ end
267
+ end
268
+
269
+ def determine_commit_span
270
+ if(@first_commit.nil? || @last_commit.nil?)
271
+ if(name_args.size > 0)
272
+ if(name_args.first.start_with?('#'))
273
+ @first_commit, @last_commit = discover_commits(name_args.first)
274
+ elsif(name_args.first.include?(".."))
275
+ @first_commit, @last_commit = name_args.first.split("..")
276
+ else
277
+ @first_commit = "#{name_args.first}^1"
278
+ @last_commit = name_args.first
279
+ end
280
+ else
281
+ @first_commit, @last_commit = discover_commits
282
+ end
283
+ end
284
+ [@first_commit, @last_commit]
285
+ end
286
+
287
+ def discover_commits(pull_num = nil)
288
+ match = "pull request #{pull_num}"
289
+ commit = git.log.detect{|log| log.message.include?(match)}
290
+ if(commit)
291
+ ["#{commit}^1",commit]
292
+ else
293
+ raise "Unable to locate last pull request"
294
+ end
295
+ end
296
+
297
+ def discover_changed(type, first_commit, last_commit)
298
+ changed = []
299
+ git.diff(first_commit, last_commit).stats[:files].keys.each do |path|
300
+ if(path.start_with?(type.to_s))
301
+ changed << path.sub(/^#{type.to_s}\/?/, '')
302
+ end
303
+ end
304
+ changed.uniq
305
+ end
306
+
307
+ def upload_changes(type, changed)
308
+ raise "Unsupported upload change type: #{type}" unless [:roles, :data_bags].include?(type.to_sym)
309
+ ui.highline.say "#{ui.highline.color("Uploading #{type.to_s.gsub('_', ' ')}:", HighLine::GREEN)} "
310
+ unless(changed.empty?)
311
+ changed.each do |item|
312
+ send("upload_#{type}", item)
313
+ ui.highline.say "#{File.basename(item).sub(/\.(rb|json)/, '')} "
314
+ end
315
+ else
316
+ ui.highline.say "no #{type.to_s.gsub('_', ' ').sub(/s$/, '')} changes detected "
317
+ end
318
+ ui.highline.say "\n"
319
+ end
320
+
321
+ def loader(type)
322
+ if(type == :roles)
323
+ @role_loader ||= Chef::Knife::Core::ObjectLoader.new(Chef::Role, ui)
324
+ elsif(type == :data_bags)
325
+ @role_loader ||= Chef::Knife::Core::ObjectLoader.new(Chef::DataBag, ui)
326
+ else
327
+ raise 'Unsupported load type'
328
+ end
329
+ end
330
+
331
+ end
332
+ end
@@ -0,0 +1,54 @@
1
+ require 'knife-table/helpers'
2
+
3
+ module KnifeTable
4
+ class TableSet < Chef::Knife
5
+
6
+ include KnifeTable::Helpers
7
+
8
+ deps do
9
+ require 'git'
10
+ require 'chef/knife/core/object_loader'
11
+ end
12
+
13
+ banner 'knife table set NEW_FEATURE_OR_FIX'
14
+
15
+ option :cookbooks,
16
+ :short => '-c [COOKBOOK,COOKBOOK,...]',
17
+ :long => '--cookbooks [COOKBOOK,COOKBOOK,...]',
18
+ :description => 'Automatically bump patch version on provided cookbooks'
19
+
20
+ option :branch_prefix,
21
+ :short => '-p PREFIX',
22
+ :long => '--branch-prefix PREFIX',
23
+ :description => 'Set prefix for branch name',
24
+ :default => 'WIP-'
25
+
26
+ option :bump_type,
27
+ :short => '-b TYPE',
28
+ :long => '--bump-type TYPE',
29
+ :description => 'Type of version bump (major, minor, patch)',
30
+ :default => 'patch'
31
+
32
+ def initialize(*args)
33
+ super
34
+ @cookbooks = config[:cookbooks].to_s.split(',').map(&:strip)
35
+ end
36
+
37
+ def run
38
+ ui.msg ui.highline.color("#{' ' * 10}** Knife Table: New place setting **", [HighLine::GREEN, HighLine::BOLD])
39
+ branch_name = "#{config[:branch_prefix]}#{name_args.join('_').downcase}"
40
+ ui.highline.say "Creating new work branch (#{branch_name}): "
41
+ git.branch(branch_name).create
42
+ ui.highline.say "done"
43
+ git.checkout(branch_name)
44
+
45
+ unless(@cookbooks.empty?)
46
+ bumper = KnifeSpork::SporkBump.new
47
+ @cookbooks.each do |cookbook|
48
+ bumper.patch(cookbook_path, cookbook, config[:bump_type])
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1 @@
1
+ require 'knife-table/version'
@@ -0,0 +1,11 @@
1
+ module KnifeTable
2
+ module Helpers
3
+ def git
4
+ @git ||= Git.open(File.dirname(cookbook_path))
5
+ end
6
+
7
+ def cookbook_path
8
+ Chef::Config[:cookbook_path].first
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module KnifeTable
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-table
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Roberts
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: knife-spork
16
+ requirement: &16438040 !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: *16438040
25
+ description: Chef's table
26
+ email: chrisroberts.code@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - CHANGELOG.md
32
+ - README.md
33
+ - knife-table.gemspec
34
+ - lib/knife-table/version.rb
35
+ - lib/knife-table/helpers.rb
36
+ - lib/chef/knife/data_holder.rb
37
+ - lib/chef/knife/table_serve.rb
38
+ - lib/chef/knife/table_clear.rb
39
+ - lib/chef/knife/table_set.rb
40
+ - lib/knife-table.rb
41
+ homepage: http://github.com/heavywater/knife-table
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.17
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Help chef set and serve the table
65
+ test_files: []
66
+ has_rdoc: