knife-table 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## v0.0.3
2
+ * Add foodcritic support
3
+ * Add knife config support
4
+ * Add hub integration
5
+ * Add order command
6
+
1
7
  ## v0.0.2
2
8
  * Load data bag via DataBagItem class not DataBag
3
9
 
data/README.md CHANGED
@@ -1,18 +1,20 @@
1
- # ChefTable
1
+ # KnifeTable
2
2
 
3
- ChefTable is a knife plugin to aid in cookbook development
3
+ KnifeTable is a knife plugin to aid in cookbook development
4
4
  workflow. Its intention is to help automate versioning
5
5
  within environments and cookbook freezing based on a stable
6
- branch. Building off of the knife-spork plugin ChefTable
6
+ branch. Building off of the knife-spork plugin KnifeTable
7
7
  helps to provide consistency within the environment.
8
8
 
9
9
 
10
10
  ## Usage
11
11
 
12
- Currently, two helpers are available:
12
+ Currently, the supported workflow is as follows:
13
13
 
14
14
  `knife table set`
15
15
 
16
+ `knife table order`
17
+
16
18
  and
17
19
 
18
20
  `knife table serve`
@@ -25,13 +27,22 @@ of what is being added:
25
27
 
26
28
  `knife table set new feature`
27
29
 
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:
30
+ This will create a new working branch named 'new_feature'. A default prefix
31
+ can be added the branch name via the `-p` option. If it is known what cookbooks
32
+ will be modified, you can provide them while setting:
32
33
 
33
34
  `knife table set -c iptables,mysql new feature`
34
35
 
36
+ ### Order
37
+
38
+ Once the code has been updated, tested and is ready for review, the order
39
+ can be placed which will create a new pull request:
40
+
41
+ `knife table order`
42
+
43
+ The order option will also optionally run foodcritic and require a passing
44
+ result before proceeding with the pull request generation.
45
+
35
46
  ### Service
36
47
 
37
48
  Service works on the assumption that any new code into the stable branch (master
data/knife-table.gemspec CHANGED
@@ -1,6 +1,8 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'knife-table/version'
1
3
  Gem::Specification.new do |s|
2
4
  s.name = 'knife-table'
3
- s.version = '0.0.2'
5
+ s.version = KnifeTable::VERSION.version
4
6
  s.summary = 'Help chef set and serve the table'
5
7
  s.author = 'Chris Roberts'
6
8
  s.email = 'chrisroberts.code@gmail.com'
@@ -8,5 +10,7 @@ Gem::Specification.new do |s|
8
10
  s.description = "Chef's table"
9
11
  s.require_path = 'lib'
10
12
  s.files = Dir.glob('**/*')
11
- s.add_dependency 'knife-spork'
13
+ s.add_dependency 'knife-spork', '>= 0.1.11'
14
+ s.add_dependency 'hub', '>= 1.10.1'
15
+ s.add_dependency 'foodcritic', '>= 1.4.0'
12
16
  end
@@ -13,7 +13,7 @@ module KnifeTable
13
13
  banner 'knife table clear'
14
14
 
15
15
  def run
16
-
16
+ ui.fatal "Not currently supported"
17
17
  end
18
18
 
19
19
  end
@@ -0,0 +1,154 @@
1
+ require 'open3'
2
+ require 'knife-table/helpers'
3
+
4
+ module KnifeTable
5
+ class TableOrder < Chef::Knife
6
+
7
+ include KnifeTable::Helpers
8
+
9
+ deps do
10
+ require 'git'
11
+ require 'hub'
12
+ end
13
+
14
+ banner 'knife table order [BRANCH]'
15
+
16
+ option :upstream_user,
17
+ :short => '-u USER',
18
+ :long => '--upstream-user USER',
19
+ :description => 'Username for upstream github account'
20
+
21
+ option :upstream_branch,
22
+ :short => '-b BRANCH',
23
+ :long => '--upstream-branch BRANCH',
24
+ :description => 'Upstream branch name'
25
+
26
+ option :title,
27
+ :short => '-t TITLE',
28
+ :long => '--title TITLE',
29
+ :description => 'Title for pull request'
30
+
31
+ option :foodcritic,
32
+ :short => '-f',
33
+ :long => '--foodcritic',
34
+ :description => 'Pass foodcritic before generating pull request',
35
+ :boolean => true
36
+
37
+ option :foodcritic_fail_on,
38
+ :short => '-x correctness,any,~FC014',
39
+ :long => '--foodcritic-fail-on correctness,any,~FC014',
40
+ :description => 'Set what foodcritic should fail on',
41
+ :proc => lambda{|v| v.split(',').strip}
42
+
43
+
44
+ def run
45
+ ui.msg ui.highline.color("#{' ' * 10}** Knife Table: Placing Order **", [HighLine::GREEN, HighLine::BOLD])
46
+ check_config_options
47
+ if(config[:foodcritic])
48
+ fail_on = Array(config[:foodcritic_fail_on]).map{|s| "-f #{s}"}.join(' ')
49
+ cookbooks = discover_changed(:cookbooks, 'master', 'HEAD').map{|c| c.split('/').first}
50
+ pass = true
51
+ cookbooks.each do |cookbook|
52
+ res = system("foodcritic #{fail_on} #{File.join(cookbook_path, cookbook)}")
53
+ pass = res unless res
54
+ end
55
+ unless(pass)
56
+ ui.fatal "Modifications do not currently pass foodcritic!"
57
+ exit 1
58
+ end
59
+ end
60
+
61
+ # TODO: Update this to not shell out
62
+ cmd = "hub pull-request \"#{title}\" -b #{@upstream}:#{config[:upstream_branch]} -h #{local_user}:#{local_branch}"
63
+ output = ''
64
+ err = nil
65
+ unless(File.exists?(File.join(ENV['HOME'], '.config', 'hub')))
66
+ g_config = Hub::GitHubAPI::Configuration.new(nil)
67
+ g_user = g_config.prompt 'Github username'
68
+ g_pass = g_config.prompt_password 'Github', g_user
69
+ end
70
+ res = Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
71
+ if(g_user)
72
+ stdin.puts g_user
73
+ stdin.puts g_pass
74
+ end
75
+ output << stdout.readlines.last.to_s
76
+ err = stderr.readlines.last.to_s
77
+ wait_thr.value
78
+ end
79
+ output.strip!
80
+ if(res.success?)
81
+ ui.msg "New pull request: #{output}"
82
+ else
83
+ ui.error err.to_s.empty? ? 'Failed to create pull request' : err
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def title
90
+ config[:title] || local_branch.gsub('_', ' ')
91
+ end
92
+
93
+ def local_branch
94
+ unless(@branch)
95
+ if(name_args.size > 0)
96
+ @branch = name_args.first
97
+ else
98
+ @branch = git.current_branch
99
+ end
100
+ end
101
+ @branch
102
+ end
103
+
104
+ def local_user
105
+ unless(@local)
106
+ l = %x{git remote -v}.split("\n").find_all{|r|
107
+ r.include?('github.com')
108
+ }.map{|r|
109
+ Array(r.scan(%r{:[^/]+}).first).first
110
+ }.compact.map{|r|
111
+ r.sub(':', '')
112
+ }.uniq.sort - [@upstream]
113
+ if(l.size > 1)
114
+ @local = ask_user_local(l)
115
+ elsif(l.size < 1)
116
+ @local = @upstream
117
+ else
118
+ @local = l.first
119
+ end
120
+ end
121
+ @local
122
+ end
123
+
124
+ def ask_user_local(locals)
125
+ l = nil
126
+ ui.msg 'Please select your local user:'
127
+ while(l.nil?)
128
+ locals.each_with_index do |name, idx|
129
+ ui.msg "#{idx + 1}. #{name}"
130
+ end
131
+ res = ui.ask_question "Enter number of local user [1-#{local.size}]:"
132
+ if(locals[res.to_i + 1])
133
+ l = locals[res.to_i + 1]
134
+ else
135
+ ui.warn "Invalid selection."
136
+ end
137
+ end
138
+ l
139
+ end
140
+
141
+ def check_config_options
142
+ %w(upstream_user upstream_branch title foodcritic foodcritic_fail_on).each do |key|
143
+ config[key.to_sym] ||= Chef::Config["table_set_#{key}".to_sym]
144
+ end
145
+ @upstream = config[:upstream_user]
146
+ unless(@upstream)
147
+ ui.fatal "Upstream user is REQUIRED"
148
+ exit 1
149
+ end
150
+ config[:foodcritic_fail_on] ||= 'correctness'
151
+ config[:upstream_branch] ||= 'master'
152
+ end
153
+ end
154
+ end
@@ -32,13 +32,11 @@ module KnifeTable
32
32
  option :git_branch,
33
33
  :short => '-b BRANCH',
34
34
  :long => '--git-branch BRANCH',
35
- :default => 'master',
36
35
  :description => 'Set working branch'
37
36
 
38
37
  option :git_remote_name,
39
38
  :short => '-r NAME',
40
39
  :long => '--git-remote-name NAME',
41
- :default => 'origin',
42
40
  :description => 'Remote repo name'
43
41
 
44
42
  option :git_autocommit,
@@ -65,19 +63,15 @@ module KnifeTable
65
63
  :boolean => true,
66
64
  :description => 'Upload any changed data bags'
67
65
 
68
- def initialize(*args)
69
- super
70
- @environments = config[:environments].to_s.split(",").map(&:strip)
71
- end
72
-
73
66
  def run
67
+ check_config_options
74
68
  sanity_checks
75
69
  cookbooks = discover_changed(:cookbooks, *determine_commit_span).map{|c|c.split('/').first}
76
70
  roles = discover_changed(:roles, *determine_commit_span) if config[:upload_roles]
77
71
  data_bags = discover_changed(:data_bags, *determine_commit_span) if config[:upload_data_bags]
78
72
 
79
73
  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)
74
+ ui.highline.say ui.highline.color("Discovered cookbooks staged for freeze: #{cookbooks.join(', ')}", HighLine::CYAN) unless cookbooks.nil? || cookbooks.empty?
81
75
  ui.highline.say ui.highline.color("Environments staged to be updated: #{@environments.join(', ')}", HighLine::CYAN) unless @environments.empty?
82
76
  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
77
  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?
@@ -224,6 +218,7 @@ module KnifeTable
224
218
  def upload_roles(role)
225
219
  role_load = loader(:roles).load_from('roles', role)
226
220
  role_load.save
221
+ role_load
227
222
  end
228
223
 
229
224
  def upload_data_bags(bag)
@@ -232,6 +227,7 @@ module KnifeTable
232
227
  dbag.data_bag(bag.split('/').first)
233
228
  dbag.raw_data = data_bag_load
234
229
  dbag.save
230
+ dbag
235
231
  end
236
232
 
237
233
  private
@@ -297,23 +293,13 @@ module KnifeTable
297
293
  end
298
294
  end
299
295
 
300
- def discover_changed(type, first_commit, last_commit)
301
- changed = []
302
- git.diff(first_commit, last_commit).stats[:files].keys.each do |path|
303
- if(path.start_with?(type.to_s))
304
- changed << path.sub(/^#{type.to_s}\/?/, '')
305
- end
306
- end
307
- changed.uniq
308
- end
309
-
310
296
  def upload_changes(type, changed)
311
297
  raise "Unsupported upload change type: #{type}" unless [:roles, :data_bags].include?(type.to_sym)
312
298
  ui.highline.say "#{ui.highline.color("Uploading #{type.to_s.gsub('_', ' ')}:", HighLine::GREEN)} "
313
299
  unless(changed.empty?)
314
300
  changed.each do |item|
315
- send("upload_#{type}", item)
316
- ui.highline.say "#{File.basename(item).sub(/\.(rb|json)/, '')} "
301
+ thing = send("upload_#{type}", item)
302
+ ui.highline.say "#{thing.is_a?(Chef::Role) ? thing.name : "#{thing.data_bag}::#{thing.id}"} "
317
303
  end
318
304
  else
319
305
  ui.highline.say "no #{type.to_s.gsub('_', ' ').sub(/s$/, '')} changes detected "
@@ -331,5 +317,14 @@ module KnifeTable
331
317
  end
332
318
  end
333
319
 
320
+ def check_config_options
321
+ %w(environments git_autopush git_tag git_branch git_remote_name
322
+ git_autocommit autoproceed upload_roles upload_data_bags).each do |key|
323
+ config[key.to_sym] ||= Chef::Config["table_set_#{key}".to_sym]
324
+ end
325
+ @environments = config[:environments].to_s.split(",").map(&:strip)
326
+ config[:git_branch] ||= 'master'
327
+ config[:git_remote_name] ||= 'origin'
328
+ end
334
329
  end
335
330
  end
@@ -20,23 +20,24 @@ module KnifeTable
20
20
  option :branch_prefix,
21
21
  :short => '-p PREFIX',
22
22
  :long => '--branch-prefix PREFIX',
23
- :description => 'Set prefix for branch name',
24
- :default => 'WIP-'
23
+ :description => 'Set prefix for branch name'
25
24
 
26
25
  option :bump_type,
27
26
  :short => '-b TYPE',
28
27
  :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
28
+ :description => 'Type of version bump (major, minor, patch)'
36
29
 
37
30
  def run
38
31
  ui.msg ui.highline.color("#{' ' * 10}** Knife Table: New place setting **", [HighLine::GREEN, HighLine::BOLD])
32
+ if(name_args.empty?)
33
+ ui.fatal "Feature description must be provided"
34
+ exit 1
35
+ end
36
+ check_config_options
37
+ check_current_branch!
38
+ check_up_to_date!
39
39
  branch_name = "#{config[:branch_prefix]}#{name_args.join('_').downcase}"
40
+ check_branch_conflict!(branch_name)
40
41
  ui.highline.say "Creating new work branch (#{branch_name}): "
41
42
  git.branch(branch_name).create
42
43
  ui.highline.say "done"
@@ -50,5 +51,36 @@ module KnifeTable
50
51
  end
51
52
  end
52
53
 
54
+ private
55
+
56
+ def check_current_branch!
57
+ unless(git.current_branch == 'master')
58
+ ui.fatal "Set requires master branch to be checked out. Currently on: #{git.current_branch}"
59
+ exit 1
60
+ end
61
+ end
62
+
63
+ def check_branch_conflict!(name)
64
+ conflict = git.branches.map(&:full).detect do |b|
65
+ b == name || b.sub(%r{remotes/[^/]+/}, '') == name
66
+ end
67
+ if(conflict)
68
+ ui.fatal "Failed to create topic branch. Already exists: #{conflict}"
69
+ exit 1
70
+ end
71
+ end
72
+
73
+ def check_up_to_date!
74
+ # TODO: fetch/merge master to ensure up to date?
75
+ end
76
+
77
+ def check_config_options
78
+ %w(cookbooks branch_prefix bump_type).each do |key|
79
+ config[key.to_sym] ||= Chef::Config["table_set_#{key}".to_sym]
80
+ end
81
+ @cookbooks = config[:cookbooks].to_s.split(',').map(&:strip)
82
+ config[:bump_type] ||= 'patch'
83
+ end
84
+
53
85
  end
54
86
  end
@@ -7,5 +7,15 @@ module KnifeTable
7
7
  def cookbook_path
8
8
  Chef::Config[:cookbook_path].first
9
9
  end
10
+
11
+ def discover_changed(type, first_commit, last_commit)
12
+ changed = []
13
+ git.diff(first_commit, last_commit).stats[:files].keys.each do |path|
14
+ if(path.start_with?(type.to_s))
15
+ changed << path.sub(/^#{type.to_s}\/?/, '')
16
+ end
17
+ end
18
+ changed.uniq
19
+ end
10
20
  end
11
21
  end
@@ -1,3 +1,5 @@
1
1
  module KnifeTable
2
- VERSION = '0.0.2'
2
+ class Version < Gem::Version
3
+ end
4
+ VERSION = Version.new('0.0.3')
3
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,32 +9,54 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-12 00:00:00.000000000 Z
12
+ date: 2012-07-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: knife-spork
16
- requirement: &20490380 !ruby/object:Gem::Requirement
16
+ requirement: &5700840 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 0.1.11
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *20490380
24
+ version_requirements: *5700840
25
+ - !ruby/object:Gem::Dependency
26
+ name: hub
27
+ requirement: &5716660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.10.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *5716660
36
+ - !ruby/object:Gem::Dependency
37
+ name: foodcritic
38
+ requirement: &5716120 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.4.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *5716120
25
47
  description: Chef's table
26
48
  email: chrisroberts.code@gmail.com
27
49
  executables: []
28
50
  extensions: []
29
51
  extra_rdoc_files: []
30
52
  files:
53
+ - knife-table-0.0.3.gem
31
54
  - CHANGELOG.md
32
- - knife-table-0.0.2.gem
33
55
  - README.md
34
56
  - knife-table.gemspec
35
57
  - lib/knife-table/version.rb
36
58
  - lib/knife-table/helpers.rb
37
- - lib/chef/knife/data_holder.rb
59
+ - lib/chef/knife/table_order.rb
38
60
  - lib/chef/knife/table_serve.rb
39
61
  - lib/chef/knife/table_clear.rb
40
62
  - lib/chef/knife/table_set.rb
@@ -1,54 +0,0 @@
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