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.
- data/lib/chef/application.rb +13 -1
- data/lib/chef/application/client.rb +4 -2
- data/lib/chef/application/knife.rb +26 -2
- data/lib/chef/application/solo.rb +4 -3
- data/lib/chef/client.rb +2 -1
- data/lib/chef/cookbook_version.rb +2 -1
- data/lib/chef/knife.rb +129 -37
- data/lib/chef/knife/cookbook_metadata.rb +8 -3
- data/lib/chef/knife/cookbook_site_install.rb +3 -127
- data/lib/chef/knife/cookbook_site_vendor.rb +46 -0
- data/lib/chef/knife/cookbook_test.rb +13 -2
- data/lib/chef/knife/core/cookbook_scm_repo.rb +149 -0
- data/lib/chef/knife/core/generic_presenter.rb +184 -0
- data/lib/chef/knife/core/node_editor.rb +127 -0
- data/lib/chef/knife/core/node_presenter.rb +103 -0
- data/lib/chef/knife/core/object_loader.rb +75 -0
- data/lib/chef/knife/{subcommand_loader.rb → core/subcommand_loader.rb} +1 -1
- data/lib/chef/knife/core/text_formatter.rb +100 -0
- data/lib/chef/knife/{ui.rb → core/ui.rb} +53 -73
- data/lib/chef/knife/data_bag_from_file.rb +8 -2
- data/lib/chef/knife/environment_from_file.rb +14 -3
- data/lib/chef/knife/node_edit.rb +14 -105
- data/lib/chef/knife/node_from_file.rb +6 -1
- data/lib/chef/knife/node_show.rb +6 -0
- data/lib/chef/knife/role_from_file.rb +6 -1
- data/lib/chef/knife/search.rb +34 -19
- data/lib/chef/knife/status.rb +15 -1
- data/lib/chef/mixin/recipe_definition_dsl_core.rb +1 -4
- data/lib/chef/mixin/shell_out.rb +1 -0
- data/lib/chef/node.rb +17 -5
- data/lib/chef/resource.rb +42 -19
- data/lib/chef/rest.rb +14 -6
- data/lib/chef/rest/auth_credentials.rb +1 -1
- data/lib/chef/rest/rest_request.rb +26 -1
- data/lib/chef/runner.rb +2 -9
- data/lib/chef/version.rb +1 -1
- metadata +11 -7
- data/lib/chef/knife/bootstrap/client-install.vbs +0 -80
- data/lib/chef/knife/bootstrap/windows-gems.erb +0 -34
- data/lib/chef/knife/windows_bootstrap.rb +0 -157
data/lib/chef/application.rb
CHANGED
@@ -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}
|
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
|
-
|
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 => "
|
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.
|
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
|
-
|
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
|
data/lib/chef/client.rb
CHANGED
@@ -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
|
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
|
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]
|
data/lib/chef/knife.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
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
|
322
|
-
|
323
|
-
|
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
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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.
|
342
|
-
exit 30
|
398
|
+
ui.error "#{e.class.name}: #{e.message}"
|
343
399
|
end
|
400
|
+
end
|
344
401
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
when
|
349
|
-
|
350
|
-
|
351
|
-
|
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.
|
354
|
-
|
427
|
+
ui.error response.message
|
428
|
+
ui.info "Response: #{format_rest_error(response)}"
|
355
429
|
end
|
356
430
|
end
|
357
431
|
|
358
|
-
def
|
359
|
-
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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
|
|