chef 0.9.0.b01 → 0.9.0.b02

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chef might be problematic. Click here for more details.

@@ -32,13 +32,13 @@ class Chef::Cookbook::Metadata
32
32
  end
33
33
 
34
34
  def _parse(str="")
35
- @major, @minor, @patch = case str
35
+ @major, @minor, @patch = case str.to_s
36
36
  when /^(\d+)\.(\d+)\.(\d+)$/
37
37
  [ $1.to_i, $2.to_i, $3.to_i ]
38
38
  when /^(\d+)\.(\d+)$/
39
39
  [ $1.to_i, $2.to_i, 0 ]
40
40
  else
41
- raise "Metadata version does not match 'x.y.z' or 'x.y'"
41
+ raise "Metadata version '#{str.to_s}' does not match 'x.y.z' or 'x.y'"
42
42
  end
43
43
  end
44
44
 
@@ -0,0 +1,128 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/cache/checksum'
20
+ require 'chef/mixin/shell_out'
21
+
22
+ class Chef
23
+ class Cookbook
24
+ class SyntaxCheck
25
+ include Chef::Mixin::ShellOut
26
+
27
+ attr_reader :cookbook_path
28
+
29
+ def self.for_cookbook(cookbook_name, cookbook_path=nil)
30
+ cookbook_path ||= Chef::Config.cookbook_path
31
+ unless cookbook_path
32
+ raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given"
33
+ end
34
+ new(File.join(cookbook_path, cookbook_name.to_s))
35
+ end
36
+
37
+ def initialize(cookbook_path)
38
+ @cookbook_path = cookbook_path
39
+ end
40
+
41
+ def cache
42
+ Chef::Cache::Checksum.instance
43
+ end
44
+
45
+ def ruby_files
46
+ Dir[File.join(cookbook_path, '**', '*.rb')]
47
+ end
48
+
49
+ def untested_ruby_files
50
+ ruby_files.reject do |file|
51
+ if validated?(file)
52
+ Chef::Log.debug("ruby file #{file} is unchanged, skipping syntax check")
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+ end
59
+
60
+ def template_files
61
+ Dir[File.join(cookbook_path, '**', '*.erb')]
62
+ end
63
+
64
+ def untested_template_files
65
+ template_files.reject do |file|
66
+ if validated?(file)
67
+ Chef::Log.debug("template #{file} is unchanged, skipping syntax check")
68
+ true
69
+ else
70
+ false
71
+ end
72
+ end
73
+ end
74
+
75
+ def validated?(file)
76
+ !!cache.lookup_checksum(cache_key(file), File.stat(file))
77
+ end
78
+
79
+ def validated(file)
80
+ cache.generate_checksum(cache_key(file), file, File.stat(file))
81
+ end
82
+
83
+ def cache_key(file)
84
+ @cache_keys ||= {}
85
+ @cache_keys[file] ||= cache.generate_key(file, "chef-test")
86
+ end
87
+
88
+ def validate_ruby_files
89
+ untested_ruby_files.each do |ruby_file|
90
+ return false unless validate_ruby_file(ruby_file)
91
+ validated(ruby_file)
92
+ end
93
+ end
94
+
95
+ def validate_templates
96
+ untested_template_files.each do |template|
97
+ return false unless validate_template(template)
98
+ validated(template)
99
+ end
100
+ end
101
+
102
+ def validate_template(erb_file)
103
+ Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
104
+ result = shell_out("sh -c 'erubis -x #{erb_file} | ruby -c'")
105
+ result.error!
106
+ true
107
+ rescue Chef::Exceptions::ShellCommandFailed
108
+ file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
109
+ Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:")
110
+ result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
111
+ false
112
+ end
113
+
114
+ def validate_ruby_file(ruby_file)
115
+ Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
116
+ result = shell_out("ruby -c #{ruby_file}")
117
+ result.error!
118
+ true
119
+ rescue Chef::Exceptions::ShellCommandFailed
120
+ file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
121
+ Chef::Log.fatal("Cookbook file #{file_relative_path} has a ruby syntax error:")
122
+ result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
123
+ false
124
+ end
125
+
126
+ end
127
+ end
128
+ end
@@ -664,6 +664,23 @@ class Chef
664
664
  chef_server_rest.get_rest('cookbooks')
665
665
  end
666
666
 
667
+ ##
668
+ # Given a +cookbook_name+, get a list of all versions that exist on the
669
+ # server.
670
+ # ===Returns
671
+ # [String]:: Array of cookbook versions, which are strings like 'x.y.z'
672
+ # nil:: if the cookbook doesn't exist. an error will also be logged.
673
+ def self.available_versions(cookbook_name)
674
+ chef_server_rest.get_rest("cookbooks/#{cookbook_name}").values.flatten
675
+ rescue Net::HTTPServerException => e
676
+ if e.to_s =~ /^404/
677
+ Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
678
+ nil
679
+ else
680
+ raise
681
+ end
682
+ end
683
+
667
684
  # Get the newest version of all cookbooks
668
685
  def self.latest_cookbooks
669
686
  chef_server_rest.get_rest('cookbooks/_latest')
@@ -16,6 +16,9 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  #
19
+
20
+ require 'chef/log'
21
+
19
22
  class Chef
20
23
 
21
24
  # FileAccessControl objects set the owner, group and mode of +file+ to
@@ -55,7 +58,7 @@ class Chef
55
58
 
56
59
  # Workaround the fact that Ruby's Etc module doesn't believe in negative
57
60
  # uids, so negative uids show up as the diminished radix complement of
58
- # the maximum fixnum size. For example, a uid of -2 is reported as
61
+ # a uint. For example, a uid of -2 is reported as 4294967294
59
62
  def dimished_radix_complement(int)
60
63
  if int > UID_MAX
61
64
  int - UINT
@@ -71,10 +74,11 @@ class Chef
71
74
  elsif resource.owner.kind_of?(Integer)
72
75
  resource.owner
73
76
  else
77
+ Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
74
78
  raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer"
75
79
  end
76
80
  rescue ArgumentError
77
- raise Chef::Exceptions::UserIDNotFound, "cannot resolve user id for '#{resource.owner}'"
81
+ raise Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?"
78
82
  end
79
83
 
80
84
  def set_owner
@@ -92,10 +96,11 @@ class Chef
92
96
  elsif resource.group.kind_of?(Integer)
93
97
  resource.group
94
98
  else
99
+ Chef::Log.error("The `group` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
95
100
  raise ArgumentError, "cannot resolve #{resource.group.inspect} to gid, group must be a string or integer"
96
101
  end
97
102
  rescue ArgumentError
98
- raise Chef::Exceptions::GroupIDNotFound, "cannot resolve group id for '#{resource.group}'"
103
+ raise Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?"
99
104
  end
100
105
 
101
106
  def set_group
@@ -46,7 +46,7 @@ class Chef
46
46
  load_commands
47
47
  @sub_classes.keys.sort.each do |snake_case|
48
48
  klass_instance = build_sub_class(snake_case)
49
- klass_instance.parse_options
49
+ klass_instance.parse_options([])
50
50
  puts klass_instance.opt_parser
51
51
  puts
52
52
  end
@@ -98,6 +98,14 @@ class Chef
98
98
  klass_instance
99
99
  end
100
100
 
101
+ def parse_options(args)
102
+ super
103
+ rescue OptionParser::InvalidOption => e
104
+ puts "Error: " + e.to_s
105
+ show_usage
106
+ exit(1)
107
+ end
108
+
101
109
  def ask_question(question, opts={})
102
110
  question = question + "[#{opts[:default]}] " if opts[:default]
103
111
 
@@ -159,17 +167,17 @@ class Chef
159
167
  def output(data)
160
168
  case config[:format]
161
169
  when "json", nil
162
- puts JSON.pretty_generate(data)
170
+ stdout.puts JSON.pretty_generate(data)
163
171
  when "yaml"
164
172
  require 'yaml'
165
- puts YAML::dump(data)
173
+ stdout.puts YAML::dump(data)
166
174
  when "text"
167
175
  # If you were looking for some attribute and there is only one match
168
176
  # just dump the attribute value
169
177
  if data.length == 1 and config[:attribute]
170
- puts data.values[0]
178
+ stdout.puts data.values[0]
171
179
  else
172
- pp data
180
+ PP.pp(data, stdout)
173
181
  end
174
182
  else
175
183
  raise ArgumentError, "Unknown output format #{config[:format]}"
@@ -224,10 +232,11 @@ class Chef
224
232
  parse_output ? JSON.parse(output) : output
225
233
  end
226
234
 
227
- def confirm(question)
235
+ def confirm(question, append_instructions=true)
228
236
  return true if config[:yes]
229
237
 
230
- print "#{question}? (Y/N) "
238
+ print question
239
+ print "? (Y/N) " if append_instructions
231
240
  answer = stdin.readline
232
241
  answer.chomp!
233
242
  case answer
@@ -243,6 +252,10 @@ class Chef
243
252
  end
244
253
  end
245
254
 
255
+ def show_usage
256
+ stdout.puts("USAGE: " + self.opt_parser.to_s)
257
+ end
258
+
246
259
  def load_from_file(klass, from_file, bag=nil)
247
260
  relative_path = ""
248
261
  if klass == Chef::Role
@@ -1,6 +1,7 @@
1
1
  #
2
2
  # Author:: Adam Jacob (<adam@opscode.com>)
3
- # Copyright:: Copyright (c) 2009 Opscode, Inc.
3
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
4
+ # Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
4
5
  # License:: Apache License, Version 2.0
5
6
  #
6
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +18,7 @@
17
18
  #
18
19
 
19
20
  require 'chef/knife'
21
+ require 'chef/knife/cookbook_delete'
20
22
 
21
23
  class Chef
22
24
  class Knife
@@ -24,13 +26,26 @@ class Chef
24
26
 
25
27
  banner "Sub-Command: cookbook bulk delete REGEX (options)"
26
28
 
27
- def run
28
- if @name_args.length < 1
29
+ def run
30
+ unless regex_str = @name_args.first
29
31
  Chef::Log.fatal("You must supply a regular expression to match the results against")
30
32
  exit 42
31
- else
32
- bulk_delete(Chef::CookbookVersion, "cookbook", "cookbook", rest.get_rest("cookbooks"), @name_args[0]) do |name, cookbook|
33
- rest.delete_rest(cookbook)
33
+ end
34
+
35
+ regex = Regexp.new(regex_str)
36
+
37
+ all_cookbooks = Chef::CookbookVersion.list
38
+ cookbooks_names = all_cookbooks.keys.grep(regex)
39
+ cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name];hash }
40
+ output(format_list_for_display(cookbooks_to_delete))
41
+
42
+ confirm("Do you really want to delete these cookbooks? All versions will be deleted. (Y/N) ", false)
43
+
44
+ cookbooks_names.each do |cookbook_name|
45
+ versions = rest.get_rest("cookbooks/#{cookbook_name}").values.flatten
46
+ versions.each do |version|
47
+ object = rest.delete_rest("cookbooks/#{cookbook_name}/#{version}")
48
+ Chef::Log.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]")
34
49
  end
35
50
  end
36
51
  end
@@ -21,21 +21,113 @@ require 'chef/knife'
21
21
  class Chef
22
22
  class Knife
23
23
  class CookbookDelete < Knife
24
+
25
+ option :all, :short => '-a', :long => '--all', :boolean => true, :description => 'delete all versions'
24
26
 
25
27
  banner "Sub-Command: cookbook delete COOKBOOK VERSION (options)"
26
28
 
27
29
  def run
28
- delete_object(Chef::CookbookVersion, "#{@name_args[0]} #{@name_args[1]}", "cookbook") do
29
- rest.delete_rest("cookbooks/#{@name_args[0]}/#{@name_args[1]}")
30
+ @cookbook_name, @version = name_args
31
+ if @cookbook_name && @version
32
+ delete_explicit_version
33
+ elsif @cookbook_name && config[:all]
34
+ delete_all_versions
35
+ elsif @cookbook_name && @version.nil?
36
+ delete_without_explicit_version
37
+ elsif @cookbook_name.nil?
38
+ show_usage
39
+ Chef::Log.fatal("You must provide the name of the cookbook to delete")
40
+ exit(1)
30
41
  end
31
42
  end
32
43
 
33
- end
34
- end
35
- end
44
+ def delete_explicit_version
45
+ delete_object(Chef::CookbookVersion, "#{@cookbook_name} version #{@version}", "cookbook") do
46
+ rest.delete_rest("cookbooks/#{@cookbook_name}/#{@version}")
47
+ end
48
+ end
49
+
50
+ def delete_all_versions
51
+ confirm("Do you really want to delete all versions of #{@cookbook_name}")
52
+ delete_all_without_confirmation
53
+ end
54
+
55
+ def delete_all_without_confirmation
56
+ # look up the available versions again just in case the user
57
+ # got to the list of versions to delete and selected 'all'
58
+ # and also a specific version
59
+ @available_versions = nil
60
+ Array(available_versions).each do |version|
61
+ delete_version_without_confirmation(version)
62
+ end
63
+ end
36
64
 
65
+ def delete_without_explicit_version
66
+ if available_versions.nil?
67
+ # we already logged an error or 2 about it, so just bail
68
+ exit(1)
69
+ elsif available_versions.size == 1
70
+ @version = available_versions.first
71
+ delete_explicit_version
72
+ else
73
+ versions_to_delete = ask_which_versions_to_delete
74
+ delete_versions_without_confirmation(versions_to_delete)
75
+ end
76
+ end
37
77
 
78
+ def available_versions
79
+ @available_versions ||= rest.get_rest("cookbooks/#{@cookbook_name}").values.flatten
80
+ rescue Net::HTTPServerException => e
81
+ if e.to_s =~ /^404/
82
+ Chef::Log.error("Cannot find a cookbook named #{@cookbook_name} to delete")
83
+ nil
84
+ else
85
+ raise
86
+ end
87
+ end
38
88
 
89
+ def ask_which_versions_to_delete
90
+ question = "Which version(s) do you want to delete?\n"
91
+ valid_responses = {}
92
+ available_versions.each_with_index do |version, index|
93
+ valid_responses[(index + 1).to_s] = version
94
+ question << "#{index + 1}. #{@cookbook_name} #{version}\n"
95
+ end
96
+ valid_responses[(available_versions.size + 1).to_s] = :all
97
+ question << "#{available_versions.size + 1}. All versions\n\n"
98
+ responses = ask_question(question).split(',').map { |response| response.strip }
99
+
100
+ if responses.empty?
101
+ Chef::Log.error("No versions specified, exiting")
102
+ exit(1)
103
+ end
104
+ versions = responses.map do |response|
105
+ if version = valid_responses[response]
106
+ version
107
+ else
108
+ Chef::Log.error("#{response} is not a valid choice, skipping it")
109
+ end
110
+ end
111
+ versions.compact
112
+ end
39
113
 
114
+ def delete_version_without_confirmation(version)
115
+ object = rest.delete_rest("cookbooks/#{@cookbook_name}/#{version}")
116
+ output(format_for_display(object)) if config[:print_after]
117
+ Chef::Log.info("Deleted cookbook[#{@cookbook_name}][#{version}]")
118
+ end
40
119
 
120
+ def delete_versions_without_confirmation(versions)
121
+ versions.each do |version|
122
+ if version == :all
123
+ delete_all_without_confirmation
124
+ break
125
+ else
126
+ delete_version_without_confirmation(version)
127
+ end
128
+ end
129
+ end
41
130
 
131
+ end
132
+ end
133
+ end
@@ -23,12 +23,13 @@ class Chef
23
23
  class Knife
24
24
  class CookbookDownload < Knife
25
25
 
26
- banner "Sub-Command: cookbook download COOKBOOK VERSION (options)"
26
+ banner "Sub-Command: cookbook download COOKBOOK [VERSION] (options)"
27
27
 
28
- option :version,
29
- :short => "-v VERSION",
30
- :long => "--version VERSION",
31
- :description => "The version of the cookbook to download"
28
+ option :latest,
29
+ :short => "-N",
30
+ :long => "--latest",
31
+ :description => "The version of the cookbook to download",
32
+ :boolean => true
32
33
 
33
34
  option :download_directory,
34
35
  :short => "-d DOWNLOAD_DIRECTORY",
@@ -45,19 +46,22 @@ class Chef
45
46
  # specificity for downloads - need to implement --platform and
46
47
  # --fqdn here
47
48
  def run
48
- if @name_args.length != 2
49
- Chef::Log.fatal("You must supply a cookbook name and version to download!")
50
- exit 42
49
+ @cookbook_name, @version = @name_args
50
+
51
+ if @cookbook_name.nil?
52
+ show_usage
53
+ Chef::Log.fatal("You must specify a cookbook name")
54
+ exit 1
55
+ elsif @version.nil?
56
+ determine_version
51
57
  end
52
58
 
53
- cookbook_name = @name_args[0]
54
- cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1]
55
- Chef::Log.info("Downloading #{cookbook_name} cookbook version #{cookbook_version}")
59
+ Chef::Log.info("Downloading #{@cookbook_name} cookbook version #{@version}")
56
60
 
57
- cookbook = rest.get_rest("cookbooks/#{cookbook_name}/#{cookbook_version}")
61
+ cookbook = rest.get_rest("cookbooks/#{@cookbook_name}/#{@version}")
58
62
  manifest = cookbook.manifest
59
63
 
60
- basedir = File.join(config[:download_directory], "#{cookbook_name}-#{cookbook.version}")
64
+ basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}")
61
65
  if File.exists?(basedir)
62
66
  if config[:force]
63
67
  Chef::Log.debug("Deleting #{basedir}")
@@ -83,6 +87,44 @@ class Chef
83
87
  Chef::Log.info("Cookbook downloaded to #{basedir}")
84
88
  end
85
89
 
90
+ def determine_version
91
+ if available_versions.size == 1
92
+ @version = available_versions.first
93
+ elsif config[:latest]
94
+ @version = available_versions.map { |v| Chef::Cookbook::Metadata::Version.new(v) }.sort.last
95
+ else
96
+ ask_which_version
97
+ end
98
+ end
99
+
100
+ def available_versions
101
+ @available_versions ||= begin
102
+ versions = Chef::CookbookVersion.available_versions(@cookbook_name).map do |version|
103
+ Chef::Cookbook::Metadata::Version.new(version)
104
+ end
105
+ versions.sort!
106
+ versions
107
+ end
108
+ #pp :available_versions => @available_versions
109
+ @available_versions
110
+ end
111
+
112
+ def ask_which_version
113
+ question = "Which version do you want to download?\n"
114
+ valid_responses = {}
115
+ available_versions.each_with_index do |version, index|
116
+ valid_responses[(index + 1).to_s] = version
117
+ question << "#{index + 1}. #{@cookbook_name} #{version}\n"
118
+ end
119
+ question += "\n"
120
+ response = ask_question(question).strip
121
+
122
+ unless @version = valid_responses[response]
123
+ Chef::Log.error("'#{response}' is not a valid value.")
124
+ exit(1)
125
+ end
126
+ end
127
+
86
128
  end
87
129
  end
88
130
  end
@@ -24,6 +24,7 @@ require 'chef/streaming_cookbook_uploader'
24
24
  require 'chef/cache/checksum'
25
25
  require 'chef/sandbox'
26
26
  require 'chef/cookbook_version'
27
+ require 'chef/cookbook/syntax_check'
27
28
  require 'chef/cookbook/file_system_file_vendor'
28
29
 
29
30
  class Chef
@@ -50,6 +51,8 @@ class Chef
50
51
  else
51
52
  config[:cookbook_path] = Chef::Config[:cookbook_path]
52
53
  end
54
+ # Ugh, manipulating globals causes bugs.
55
+ @user_cookbook_path = config[:cookbook_path]
53
56
 
54
57
  Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest) }
55
58
 
@@ -70,23 +73,12 @@ class Chef
70
73
  end
71
74
  end
72
75
 
73
- def test_ruby(cookbook_dir)
74
- Chef::Log.info("Validating ruby files")
75
- Dir[File.join(cookbook_dir, '**', '*.rb')].each do |ruby_file|
76
- test_ruby_file(cookbook_dir, ruby_file)
77
- end
78
- end
79
-
80
- def test_templates(cookbook_dir)
81
- Chef::Log.info("Validating templates")
82
- Dir[File.join(cookbook_dir, '**', '*.erb')].each do |erb_file|
83
- test_template_file(cookbook_dir, erb_file)
84
- end
85
- end
86
-
87
76
  def upload_cookbook(cookbook)
88
77
  Chef::Log.info("Saving #{cookbook.name}")
89
78
 
79
+ # Validate the cookbook before staging it or else the syntax checker's
80
+ # cache will not be helpful.
81
+ validate_cookbook(cookbook)
90
82
  # create build directory
91
83
  tmp_cookbook_dir = create_build_dir(cookbook)
92
84
 
@@ -173,10 +165,6 @@ class Chef
173
165
  end
174
166
  end
175
167
 
176
- # Validate ruby files and templates
177
- test_ruby(tmp_cookbook_dir)
178
- test_templates(tmp_cookbook_dir)
179
-
180
168
  # First, generate metadata
181
169
  Chef::Log.debug("Generating metadata")
182
170
  kcm = Chef::Knife::CookbookMetadata.new
@@ -201,30 +189,16 @@ class Chef
201
189
  end
202
190
  end
203
191
 
204
- private
205
-
206
- def test_template_file(cookbook_dir, erb_file)
207
- Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
208
- result = shell_out("sh -c 'erubis -x #{erb_file} | ruby -c'")
209
- result.error!
210
- rescue Chef::Exceptions::ShellCommandFailed
211
- file_relative_path = erb_file[/^#{Regexp.escape(cookbook_dir+File::Separator)}(.*)/, 1]
212
- Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:")
213
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
214
- exit(1)
192
+ def validate_cookbook(cookbook)
193
+ syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook.name, @user_cookbook_path)
194
+ Chef::Log.info("Validating ruby files")
195
+ exit(1) unless syntax_checker.validate_ruby_files
196
+ Chef::Log.info("Validating templates")
197
+ exit(1) unless syntax_checker.validate_templates
198
+ Chef::Log.info("Syntax OK")
199
+ true
215
200
  end
216
201
 
217
- def test_ruby_file(cookbook_dir, ruby_file)
218
- Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
219
- result = shell_out("ruby -c #{ruby_file}")
220
- result.error!
221
- rescue Chef::Exceptions::ShellCommandFailed
222
- file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_dir+File::Separator)}(.*)/, 1]
223
- Chef::Log.fatal("Cookbook file #{file_relative_path} has a syntax error:")
224
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
225
- exit(1)
226
- end
227
-
228
202
  end
229
203
  end
230
204
  end
@@ -0,0 +1,32 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+ class Chef::Knife::RecipeList < Chef::Knife
21
+
22
+ banner "Sub-Command: recipe list [PATTERN]"
23
+
24
+ def run
25
+ recipes = rest.get_rest('cookbooks/_recipes')
26
+ if pattern = @name_args.first
27
+ recipes = recipes.grep(Regexp.new(pattern))
28
+ end
29
+ output(recipes)
30
+ end
31
+
32
+ end
@@ -76,6 +76,7 @@ class Chef
76
76
  end
77
77
  r
78
78
  end
79
+ (Chef::Log.fatal("No nodes returned from search!"); exit 10) if list.length == 0
79
80
  session_from_list(list)
80
81
  end
81
82
 
@@ -201,6 +202,34 @@ class Chef
201
202
  exec("screen -c #{tf.path}")
202
203
  end
203
204
 
205
+ def tmux
206
+ begin
207
+ Chef::Mixin::Command.run_command(:command => "tmux new-session -d -s 'knife'")
208
+ rescue Chef::Exceptions::Exec
209
+ end
210
+ session.servers_for.each do |server|
211
+ begin
212
+ Chef::Mixin::Command.run_command(:command => "tmux new-window -d -n '#{server.host}' -t 'knife' 'ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}'")
213
+ rescue Chef::Exceptions::Exec
214
+ end
215
+ end
216
+ exec("tmux attach-session -t knife")
217
+ end
218
+
219
+ def macterm
220
+ require 'appscript'
221
+ Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate
222
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", :using=>:command_down)
223
+ term = Appscript.app('Terminal')
224
+ window = term.windows.first.get
225
+ session.servers_for.each do |server|
226
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", :using=>:command_down)
227
+ cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}"
228
+ Appscript.app('Terminal').do_script(cmd, :in => window.tabs.last.get)
229
+ sleep 1
230
+ end
231
+ end
232
+
204
233
  def run
205
234
  @longest = 0
206
235
 
@@ -215,6 +244,10 @@ class Chef
215
244
  interactive
216
245
  when "screen"
217
246
  screen
247
+ when "tmux"
248
+ tmux
249
+ when "macterm"
250
+ macterm
218
251
  else
219
252
  ssh_command(@name_args[1..-1].join(" "))
220
253
  end
@@ -172,7 +172,7 @@ module Shef
172
172
  @client.register
173
173
  @client.build_node #(@client.node_name, false)
174
174
 
175
- @client.sync_cookbooks
175
+ @client.sync_cookbooks({})
176
176
  end
177
177
 
178
178
  end
@@ -16,5 +16,5 @@
16
16
  # limitations under the License.
17
17
 
18
18
  class Chef
19
- VERSION = '0.9.0.b01'
19
+ VERSION = '0.9.0.b02'
20
20
  end
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 0
7
7
  - 9
8
8
  - 0
9
- - b01
10
- version: 0.9.0.b01
9
+ - b02
10
+ version: 0.9.0.b02
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Jacob
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-11 00:00:00 -07:00
18
+ date: 2010-06-13 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -283,6 +283,7 @@ files:
283
283
  - lib/chef/cookbook/metadata/version.rb
284
284
  - lib/chef/cookbook/metadata.rb
285
285
  - lib/chef/cookbook/remote_file_vendor.rb
286
+ - lib/chef/cookbook/syntax_check.rb
286
287
  - lib/chef/cookbook_loader.rb
287
288
  - lib/chef/cookbook_version.rb
288
289
  - lib/chef/couchdb.rb
@@ -344,6 +345,7 @@ files:
344
345
  - lib/chef/knife/rackspace_server_create.rb
345
346
  - lib/chef/knife/rackspace_server_delete.rb
346
347
  - lib/chef/knife/rackspace_server_list.rb
348
+ - lib/chef/knife/recipe_list.rb
347
349
  - lib/chef/knife/role_bulk_delete.rb
348
350
  - lib/chef/knife/role_create.rb
349
351
  - lib/chef/knife/role_delete.rb