chef 0.10.0.beta.5 → 0.10.0.beta.6

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.
Files changed (40) hide show
  1. data/lib/chef/application.rb +13 -1
  2. data/lib/chef/application/client.rb +4 -2
  3. data/lib/chef/application/knife.rb +26 -2
  4. data/lib/chef/application/solo.rb +4 -3
  5. data/lib/chef/client.rb +2 -1
  6. data/lib/chef/cookbook_version.rb +2 -1
  7. data/lib/chef/knife.rb +129 -37
  8. data/lib/chef/knife/cookbook_metadata.rb +8 -3
  9. data/lib/chef/knife/cookbook_site_install.rb +3 -127
  10. data/lib/chef/knife/cookbook_site_vendor.rb +46 -0
  11. data/lib/chef/knife/cookbook_test.rb +13 -2
  12. data/lib/chef/knife/core/cookbook_scm_repo.rb +149 -0
  13. data/lib/chef/knife/core/generic_presenter.rb +184 -0
  14. data/lib/chef/knife/core/node_editor.rb +127 -0
  15. data/lib/chef/knife/core/node_presenter.rb +103 -0
  16. data/lib/chef/knife/core/object_loader.rb +75 -0
  17. data/lib/chef/knife/{subcommand_loader.rb → core/subcommand_loader.rb} +1 -1
  18. data/lib/chef/knife/core/text_formatter.rb +100 -0
  19. data/lib/chef/knife/{ui.rb → core/ui.rb} +53 -73
  20. data/lib/chef/knife/data_bag_from_file.rb +8 -2
  21. data/lib/chef/knife/environment_from_file.rb +14 -3
  22. data/lib/chef/knife/node_edit.rb +14 -105
  23. data/lib/chef/knife/node_from_file.rb +6 -1
  24. data/lib/chef/knife/node_show.rb +6 -0
  25. data/lib/chef/knife/role_from_file.rb +6 -1
  26. data/lib/chef/knife/search.rb +34 -19
  27. data/lib/chef/knife/status.rb +15 -1
  28. data/lib/chef/mixin/recipe_definition_dsl_core.rb +1 -4
  29. data/lib/chef/mixin/shell_out.rb +1 -0
  30. data/lib/chef/node.rb +17 -5
  31. data/lib/chef/resource.rb +42 -19
  32. data/lib/chef/rest.rb +14 -6
  33. data/lib/chef/rest/auth_credentials.rb +1 -1
  34. data/lib/chef/rest/rest_request.rb +26 -1
  35. data/lib/chef/runner.rb +2 -9
  36. data/lib/chef/version.rb +1 -1
  37. metadata +11 -7
  38. data/lib/chef/knife/bootstrap/client-install.vbs +0 -80
  39. data/lib/chef/knife/bootstrap/windows-gems.erb +0 -34
  40. data/lib/chef/knife/windows_bootstrap.rb +0 -157
@@ -19,6 +19,7 @@ require 'chef/config'
19
19
  require 'chef/exceptions'
20
20
  require 'chef/log'
21
21
  require 'mixlib/cli'
22
+ require 'tmpdir'
22
23
 
23
24
  class Chef::Application
24
25
  include Mixlib::CLI
@@ -121,9 +122,20 @@ class Chef::Application
121
122
 
122
123
 
123
124
  class << self
125
+ def debug_stacktrace(e)
126
+ message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
127
+ filename = File.join(Dir.tmpdir, "chef-stacktrace.out")
128
+ Chef::Log.fatal("Stacktrace dumped to #{filename}")
129
+ Chef::Log.debug(message)
130
+ chef_stacktrace_out = File.open(filename, "w")
131
+ chef_stacktrace_out.puts "Generated at #{Time.now.to_s}"
132
+ chef_stacktrace_out.puts message
133
+ chef_stacktrace_out.close
134
+ true
135
+ end
136
+
124
137
  # Log a fatal error message to both STDERR and the Logger, exit the application
125
138
  def fatal!(msg, err = -1)
126
- STDERR.puts("FATAL: #{msg}")
127
139
  Chef::Log.fatal(msg)
128
140
  Process.exit err
129
141
  end
@@ -227,12 +227,14 @@ class Chef::Application::Client < Chef::Application
227
227
  raise
228
228
  rescue Exception => e
229
229
  if Chef::Config[:interval]
230
- Chef::Log.error("#{e.class}:#{e}\n#{e.backtrace.join("\n")}")
230
+ Chef::Log.error("#{e.class}: #{e}")
231
+ Chef::Application.debug_stacktrace(e)
231
232
  Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
232
233
  sleep Chef::Config[:interval]
233
234
  retry
234
235
  else
235
- raise
236
+ Chef::Application.debug_stacktrace(e)
237
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
236
238
  end
237
239
  ensure
238
240
  GC.start
@@ -18,7 +18,7 @@
18
18
  require 'chef/knife'
19
19
  require 'chef/application'
20
20
  require 'mixlib/log'
21
- require 'ohai'
21
+ require 'ohai/config'
22
22
 
23
23
  class Chef::Application::Knife < Chef::Application
24
24
 
@@ -40,6 +40,18 @@ class Chef::Application::Knife < Chef::Application
40
40
  :proc => Proc.new { verbosity_level += 1},
41
41
  :default => 0
42
42
 
43
+ option :color,
44
+ :long => '--color',
45
+ :boolean => true,
46
+ :default => true,
47
+ :description => "Use colored output"
48
+
49
+ option :no_color,
50
+ :long => '--no-color',
51
+ :boolean => true,
52
+ :default => false,
53
+ :description => "Don't use colors in the output"
54
+
43
55
  option :environment,
44
56
  :short => "-E ENVIRONMENT",
45
57
  :long => "--environment ENVIRONMENT",
@@ -97,7 +109,7 @@ class Chef::Application::Knife < Chef::Application
97
109
  :short => "-F FORMAT",
98
110
  :long => "--format FORMAT",
99
111
  :description => "Which format to use for output",
100
- :default => "json"
112
+ :default => "summary"
101
113
 
102
114
  option :version,
103
115
  :short => "-v",
@@ -107,16 +119,28 @@ class Chef::Application::Knife < Chef::Application
107
119
  :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
108
120
  :exit => 0
109
121
 
122
+
110
123
  # Run knife
111
124
  def run
112
125
  Mixlib::Log::Formatter.show_time = false
113
126
  validate_and_parse_options
127
+ quiet_traps
114
128
  Chef::Knife.run(ARGV, options)
115
129
  exit 0
116
130
  end
117
131
 
118
132
  private
119
133
 
134
+ def quiet_traps
135
+ trap("TERM") do
136
+ exit 1
137
+ end
138
+
139
+ trap("INT") do
140
+ exit 2
141
+ end
142
+ end
143
+
120
144
  def validate_and_parse_options
121
145
  # Checking ARGV validity *before* parse_options because parse_options
122
146
  # mangles ARGV in some situations
@@ -201,13 +201,14 @@ class Chef::Application::Solo < Chef::Application
201
201
  raise
202
202
  rescue Exception => e
203
203
  if Chef::Config[:interval]
204
- Chef::Log.error("#{e.class}")
205
- Chef::Log.fatal("#{e}\n#{e.backtrace.join("\n")}")
204
+ Chef::Log.error("#{e.class}: #{e}")
205
+ Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
206
206
  Chef::Log.fatal("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
207
207
  sleep Chef::Config[:interval]
208
208
  retry
209
209
  else
210
- raise
210
+ Chef::Application.debug_stacktrace(e)
211
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
211
212
  end
212
213
  ensure
213
214
  GC.start
@@ -143,6 +143,7 @@ class Chef
143
143
  def run
144
144
  run_context = nil
145
145
 
146
+ Chef::Log.info("*** Chef #{Chef::VERSION} ***")
146
147
  enforce_path_sanity
147
148
  run_ohai
148
149
  register unless Chef::Config[:solo]
@@ -151,7 +152,7 @@ class Chef
151
152
  begin
152
153
 
153
154
  run_status.start_clock
154
- Chef::Log.info("Starting Chef Run (Version #{Chef::VERSION})")
155
+ Chef::Log.info("Starting Run for #{node.name}")
155
156
  run_started
156
157
 
157
158
  run_context = setup_run_context
@@ -911,7 +911,8 @@ class Chef
911
911
  elsif segment == :templates || segment == :files
912
912
  matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+?)/(.+))")
913
913
  unless matcher
914
- Chef::Log.debug("Skipping file #{segment_file}, as it doesn't have a proper segment.")
914
+ Chef::Log.debug("Skipping file #{segment_file}, as it isn't in any of the proper directories (platform-version, platform or default)")
915
+ Chef::Log.debug("You probably need to move #{segment_file} into the 'default' sub-directory")
915
916
  next
916
917
  end
917
918
  path = matcher[1]
@@ -21,13 +21,16 @@ require 'forwardable'
21
21
  require 'chef/version'
22
22
  require 'mixlib/cli'
23
23
  require 'chef/mixin/convert_to_class_name'
24
- require 'chef/knife/subcommand_loader'
25
- require 'chef/knife/ui'
26
-
24
+ require 'chef/knife/core/subcommand_loader'
25
+ require 'chef/knife/core/ui'
26
+ require 'chef/rest'
27
27
  require 'pp'
28
28
 
29
29
  class Chef
30
30
  class Knife
31
+
32
+ Chef::REST::RESTRequest.user_agent = "Chef Knife#{Chef::REST::RESTRequest::UA_COMMON}"
33
+
31
34
  include Mixlib::CLI
32
35
  extend Chef::Mixin::ConvertToClassName
33
36
  extend Forwardable
@@ -50,7 +53,7 @@ class Chef
50
53
  def_delegator :@ui, :confirm
51
54
 
52
55
  attr_accessor :name_args
53
- attr_reader :ui
56
+ attr_accessor :ui
54
57
 
55
58
  def self.ui
56
59
  @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
@@ -92,6 +95,10 @@ class Chef
92
95
  convert_to_snake_case(name.split('::').last) unless unnamed?
93
96
  end
94
97
 
98
+ def self.common_name
99
+ snake_case_name.split('_').join(' ')
100
+ end
101
+
95
102
  # Does this class have a name? (Classes created via Class.new don't)
96
103
  def self.unnamed?
97
104
  name.nil? || name.empty?
@@ -102,7 +109,7 @@ class Chef
102
109
  end
103
110
 
104
111
  def self.load_commands
105
- subcommand_loader.load_commands
112
+ @commands_loaded ||= subcommand_loader.load_commands
106
113
  end
107
114
 
108
115
  def self.subcommands
@@ -123,6 +130,7 @@ class Chef
123
130
  # is given, only subcommands in that category are shown
124
131
  def self.list_commands(preferred_category=nil)
125
132
  load_commands
133
+
126
134
  category_desc = preferred_category ? preferred_category + " " : ''
127
135
  msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
128
136
 
@@ -133,6 +141,7 @@ class Chef
133
141
  end
134
142
 
135
143
  commands_to_show.sort.each do |category, commands|
144
+ next if category =~ /deprecated/i
136
145
  msg "** #{category.upcase} COMMANDS **"
137
146
  commands.each do |command|
138
147
  msg subcommands[command].banner if subcommands[command]
@@ -154,7 +163,7 @@ class Chef
154
163
  subcommand_class.load_deps unless want_help?(args)
155
164
  instance = subcommand_class.new(args)
156
165
  instance.configure_chef
157
- instance.run
166
+ instance.run_with_pretty_exceptions
158
167
  end
159
168
 
160
169
  def self.guess_category(args)
@@ -276,12 +285,14 @@ class Chef
276
285
 
277
286
  # Don't try to load a knife.rb if it doesn't exist.
278
287
  if config[:config_file]
279
- Chef::Config.from_file(config[:config_file])
288
+ read_config_file(config[:config_file])
280
289
  else
281
290
  # ...but do log a message if no config was found.
282
291
  self.msg("No knife configuration file found")
283
292
  end
284
293
 
294
+ config[:color] = config[:color] && !config[:no_color]
295
+
285
296
  case config[:verbosity]
286
297
  when 0
287
298
  Chef::Config[:log_level] = :error
@@ -313,50 +324,127 @@ class Chef
313
324
  end
314
325
  end
315
326
 
327
+ def read_config_file(file)
328
+ Chef::Config.from_file(file)
329
+ rescue SyntaxError => e
330
+ ui.error "You have invalid ruby syntax in your config file #{file}"
331
+ ui.info(ui.color(e.message, :red))
332
+ if file_line = e.message[/#{Regexp.escape(file)}:[\d]+/]
333
+ line = file_line[/:([\d]+)$/, 1].to_i
334
+ highlight_config_error(file, line)
335
+ end
336
+ exit 1
337
+ rescue Exception => e
338
+ ui.error "You have an error in your config file #{file}"
339
+ ui.info "#{e.class.name}: #{e.message}"
340
+ filtered_trace = e.backtrace.grep(/#{Regexp.escape(file)}/)
341
+ filtered_trace.each {|line| ui.msg(" " + ui.color(line, :red))}
342
+ if !filtered_trace.empty?
343
+ line_nr = filtered_trace.first[/#{Regexp.escape(file)}:([\d]+)/, 1]
344
+ highlight_config_error(file, line_nr.to_i)
345
+ end
346
+
347
+ exit 1
348
+ end
349
+
350
+ def highlight_config_error(file, line)
351
+ config_file_lines = []
352
+ IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
353
+ if line == 1
354
+ lines = config_file_lines[0..3]
355
+ lines[0] = ui.color(lines[0], :red)
356
+ else
357
+ lines = config_file_lines[Range.new(line - 2, line)]
358
+ lines[1] = ui.color(lines[1], :red)
359
+ end
360
+ ui.msg ""
361
+ ui.msg ui.color(" # #{file}", :white)
362
+ lines.each {|l| ui.msg(l)}
363
+ ui.msg ""
364
+ end
316
365
 
317
366
  def show_usage
318
367
  stdout.puts("USAGE: " + self.opt_parser.to_s)
319
368
  end
320
369
 
321
- def load_from_file(klass, from_file, bag=nil)
322
- relative_path = ""
323
- if klass == Chef::Role
324
- relative_path = "roles"
325
- elsif klass == Chef::Node
326
- relative_path = "nodes"
327
- elsif klass == Chef::DataBagItem
328
- relative_path = "data_bags/#{bag}"
329
- elsif klass == Chef::Environment
330
- relative_path = "environments"
370
+ def run_with_pretty_exceptions
371
+ unless self.respond_to?(:run)
372
+ ui.error "You need to add a #run method to your knife command before you can use it"
331
373
  end
374
+ run
375
+ rescue Exception => e
376
+ raise if config[:verbosity] == 2
377
+ humanize_exception(e)
378
+ exit 100
379
+ end
332
380
 
333
- relative_file = File.expand_path(File.join(Dir.pwd, relative_path, from_file))
334
- filename = nil
335
-
336
- if file_exists_and_is_readable?(from_file)
337
- filename = from_file
338
- elsif file_exists_and_is_readable?(relative_file)
339
- filename = relative_file
381
+ def humanize_exception(e)
382
+ case e
383
+ when SystemExit
384
+ raise # make sure exit passes through.
385
+ when Net::HTTPServerException, Net::HTTPFatalError
386
+ humanize_http_exception(e)
387
+ when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
388
+ ui.error "Network Error: #{e.message}"
389
+ ui.info "Check your knife configuration and network settings"
390
+ when NameError, NoMethodError
391
+ ui.error "knife encountered an unexpected error"
392
+ ui.info "This may be a bug in the '#{self.class.common_name}' knife command or plugin"
393
+ ui.info "Exception: #{e.class.name}: #{e.message}"
394
+ when Chef::Exceptions::PrivateKeyMissing
395
+ ui.error "Your private key could not be loaded from #{api_key}"
396
+ ui.info "Check your configuration file and ensure that your private key is readable"
340
397
  else
341
- ui.fatal("Cannot find file #{from_file}")
342
- exit 30
398
+ ui.error "#{e.class.name}: #{e.message}"
343
399
  end
400
+ end
344
401
 
345
- case from_file
346
- when /\.(js|json)$/
347
- Chef::JSONCompat.from_json(IO.read(filename))
348
- when /\.rb$/
349
- r = klass.new
350
- r.from_file(filename)
351
- r
402
+ def humanize_http_exception(e)
403
+ response = e.response
404
+ case response
405
+ when Net::HTTPUnauthorized
406
+ ui.error "Failed to authenticate to #{server_url} as #{username} with key #{api_key}"
407
+ ui.info "Response: #{format_rest_error(response)}"
408
+ when Net::HTTPForbidden
409
+ ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action"
410
+ ui.info "Response: #{format_rest_error(response)}"
411
+ when Net::HTTPBadRequest
412
+ ui.error "The data in your request was invalid"
413
+ ui.info "Response: #{format_rest_error(response)}"
414
+ when Net::HTTPNotFound
415
+ ui.error "The object you are looking for could not be found"
416
+ ui.info "Response: #{format_rest_error(response)}"
417
+ when Net::HTTPInternalServerError
418
+ ui.error "internal server error"
419
+ ui.info "Response: #{format_rest_error(response)}"
420
+ when Net::HTTPBadGateway
421
+ ui.error "bad gateway"
422
+ ui.info "Response: #{format_rest_error(response)}"
423
+ when Net::HTTPServiceUnavailable
424
+ ui.error "Service temporarily unavailable"
425
+ ui.info "Response: #{format_rest_error(response)}"
352
426
  else
353
- ui.fatal("File must end in .js, .json, or .rb")
354
- exit 30
427
+ ui.error response.message
428
+ ui.info "Response: #{format_rest_error(response)}"
355
429
  end
356
430
  end
357
431
 
358
- def file_exists_and_is_readable?(file)
359
- File.exists?(file) && File.readable?(file)
432
+ def username
433
+ Chef::Config[:node_name]
434
+ end
435
+
436
+ def api_key
437
+ Chef::Config[:client_key]
438
+ end
439
+
440
+ # Parses JSON from the error response sent by Chef Server and returns the
441
+ # error message
442
+ #--
443
+ # TODO: this code belongs in Chef::REST
444
+ def format_rest_error(response)
445
+ Array(Chef::JSONCompat.from_json(response.body)["error"]).join('; ')
446
+ rescue Exception
447
+ response.body
360
448
  end
361
449
 
362
450
  def create_object(object, pretty_name=nil, &block)
@@ -426,6 +514,10 @@ class Chef
426
514
  end
427
515
  end
428
516
 
517
+ def server_url
518
+ Chef::Config[:chef_server_url]
519
+ end
520
+
429
521
  end
430
522
  end
431
523
 
@@ -24,6 +24,7 @@ class Chef
24
24
  class CookbookMetadata < Knife
25
25
 
26
26
  deps do
27
+ require 'chef/cookbook_loader'
27
28
  require 'chef/cookbook/metadata'
28
29
  end
29
30
 
@@ -49,12 +50,16 @@ class Chef
49
50
  generate_metadata(cname.to_s)
50
51
  end
51
52
  else
52
- generate_metadata(@name_args[0])
53
+ cookbook_name = @name_args[0]
54
+ if cookbook_name.nil? || cookbook_name.empty?
55
+ ui.error "You must specify the cookbook to generate metadata for, or use the --all option."
56
+ exit 1
57
+ end
58
+ generate_metadata(cookbook_name)
53
59
  end
54
60
  end
55
61
 
56
62
  def generate_metadata(cookbook)
57
- ui.info("Generating Metadata")
58
63
  Array(config[:cookbook_path]).reverse.each do |path|
59
64
  file = File.expand_path(File.join(path, cookbook, 'metadata.rb'))
60
65
  if File.exists?(file)
@@ -66,7 +71,7 @@ class Chef
66
71
  end
67
72
 
68
73
  def generate_metadata_from_file(cookbook, file)
69
- Chef::Log.debug("Generating metadata for #{cookbook} from #{file}")
74
+ ui.info("Generating metadata for #{cookbook} from #{file}")
70
75
  md = Chef::Cookbook::Metadata.new
71
76
  md.name(cookbook)
72
77
  md.from_file(file)
@@ -17,143 +17,19 @@
17
17
  #
18
18
 
19
19
  require 'chef/knife'
20
- require 'chef/mixin/shell_out'
21
20
 
22
21
  class Chef
23
22
  class Knife
24
- class CookbookRepo
25
-
26
- DIRTY_REPO = /^[\s]+M/
27
-
28
- include Chef::Mixin::ShellOut
29
-
30
- attr_reader :repo_path
31
- attr_reader :default_branch
32
- attr_reader :ui
33
-
34
- def initialize(repo_path, ui, opts={})
35
- @repo_path = repo_path
36
- @ui = ui
37
- @default_branch = 'master'
38
- end
39
-
40
- def sanity_check
41
- unless ::File.directory?(repo_path)
42
- ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory")
43
- exit 1
44
- end
45
- unless git_repo?(repo_path)
46
- ui.error "The cookbook repo #{repo_path} is not a git repository."
47
- ui.info("Use `git init` to initialize a git repo")
48
- exit 1
49
- end
50
- unless branch_exists?(default_branch)
51
- ui.error "The default branch '#{default_branch}' does not exist"
52
- ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
53
- exit 1
54
- end
55
- cmd = git('status --porcelain')
56
- if cmd.stdout =~ DIRTY_REPO
57
- ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
58
- ui.msg cmd.stdout
59
- ui.info "Commit or stash your changes before importing cookbooks"
60
- exit 1
61
- end
62
- # TODO: any untracked files in the cookbook directory will get nuked later
63
- # make this an error condition also.
64
- true
65
- end
66
-
67
- def reset_to_default_state
68
- ui.info("Checking out the #{default_branch} branch.")
69
- git("checkout #{default_branch}")
70
- end
71
-
72
- def prepare_to_import(cookbook_name)
73
- branch = "chef-vendor-#{cookbook_name}"
74
- if branch_exists?(branch)
75
- ui.info("Pristine copy branch (#{branch}) exists, switching to it.")
76
- git("checkout #{branch}")
77
- else
78
- ui.info("Creating pristine copy branch #{branch}")
79
- git("checkout -b #{branch}")
80
- end
81
- end
82
-
83
- def finalize_updates_to(cookbook_name, version)
84
- if update_count = updated?(cookbook_name)
85
- ui.info "#{update_count} files updated, committing changes"
86
- git("add #{cookbook_name}")
87
- git("commit -m 'Import #{cookbook_name} version #{version}' -- #{cookbook_name}")
88
- ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}")
89
- git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}")
90
- true
91
- else
92
- ui.info("No changes made to #{cookbook_name}")
93
- false
94
- end
95
- end
96
-
97
- def merge_updates_from(cookbook_name, version)
98
- branch = "chef-vendor-#{cookbook_name}"
99
- Dir.chdir(repo_path) do
100
- if system("git merge #{branch}")
101
- ui.info("Cookbook #{cookbook_name} version #{version} successfully installed")
102
- else
103
- ui.error("You have merge conflicts - please resolve manually")
104
- ui.info("Merge status (cd #{repo_path}; git status):")
105
- system("git status")
106
- exit 3
107
- end
108
- end
109
- end
110
-
111
- def updated?(cookbook_name)
112
- update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count
113
- update_count == 0 ? nil : update_count
114
- end
115
-
116
- def branch_exists?(branch_name)
117
- git("branch --no-color").stdout.lines.any? {|l| l.include?(branch_name) }
118
- end
119
-
120
- private
121
-
122
- def git_repo?(directory)
123
- if File.directory?(File.join(directory, '.git'))
124
- return true
125
- elsif File.dirname(directory) == directory
126
- return false
127
- else
128
- git_repo?(File.dirname(directory))
129
- end
130
- end
131
-
132
- def apply_opts(opts)
133
- opts.each do |option, value|
134
- case option.to_s
135
- when 'default_branch'
136
- @default_branch = value
137
- else
138
- raise ArgumentError, "invalid option `#{option}' passed to CookbookRepo.new()"
139
- end
140
- end
141
- end
142
-
143
- def git(command)
144
- shell_out!("git #{command}", :cwd => repo_path)
145
- end
146
-
147
- end
148
23
 
149
24
  class CookbookSiteInstall < Knife
150
25
 
151
26
  deps do
152
27
  require 'chef/mixin/shell_out'
28
+ require 'chef/knife/core/cookbook_scm_repo'
153
29
  require 'chef/cookbook/metadata'
154
30
  end
155
31
 
156
- banner "knife cookbook site vendor COOKBOOK [VERSION] (options)"
32
+ banner "knife cookbook site install COOKBOOK [VERSION] (options)"
157
33
  category "cookbook site"
158
34
 
159
35
  option :deps,
@@ -192,7 +68,7 @@ class Chef
192
68
  @install_path = config[:cookbook_path].first
193
69
  ui.info "Installing #@cookbook_name to #{@install_path}"
194
70
 
195
- @repo = CookbookRepo.new(@install_path, ui, config)
71
+ @repo = CookbookSCMRepo.new(@install_path, ui, config)
196
72
  #cookbook_path = File.join(vendor_path, name_args[0])
197
73
  upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
198
74