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

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