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
@@ -0,0 +1,46 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 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
+ require 'chef/knife/cookbook_site_install'
21
+
22
+ class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall
23
+
24
+ def self.load_deps
25
+ superclass.load_deps
26
+ end
27
+
28
+ def self.options=(new_opts)
29
+ superclass.options = new_opts
30
+ end
31
+
32
+ def self.options
33
+ superclass.options
34
+ end
35
+
36
+ banner(<<-B)
37
+ *************************************************
38
+ DEPRECATED: please use knife cookbook site install
39
+ *************************************************
40
+
41
+ #{superclass.banner}
42
+ B
43
+
44
+ category 'deprecated'
45
+
46
+ end
@@ -45,16 +45,23 @@ class Chef
45
45
  def run
46
46
  config[:cookbook_path] ||= Chef::Config[:cookbook_path]
47
47
 
48
+ checked_a_cookbook = false
48
49
  if config[:all]
49
- cl = Chef::CookbookLoader.new(config[:cookbook_path])
50
- cl.each do |key, cookbook|
50
+ cookbook_loader.each do |key, cookbook|
51
+ checked_a_cookbook = true
51
52
  test_cookbook(key)
52
53
  end
53
54
  else
54
55
  @name_args.each do |cb|
56
+ puts "checking #{cb}"
57
+ next unless cookbook_loader.cookbook_exists?(cb)
58
+ checked_a_cookbook = true
55
59
  test_cookbook(cb)
56
60
  end
57
61
  end
62
+ unless checked_a_cookbook
63
+ ui.warn("No cookbooks to test in #{Array(config[:cookbook_path]).join(',')} - is your cookbook path misconfigured?")
64
+ end
58
65
  end
59
66
 
60
67
  def test_cookbook(cookbook)
@@ -77,6 +84,10 @@ class Chef
77
84
  exit(1) unless syntax_checker.validate_templates
78
85
  end
79
86
 
87
+ def cookbook_loader
88
+ @cookbook_loader ||= Chef::CookbookLoader.new(config[:cookbook_path])
89
+ end
90
+
80
91
  end
81
92
  end
82
93
  end
@@ -0,0 +1,149 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 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/mixin/shell_out'
20
+
21
+ class Chef
22
+ class Knife
23
+ class CookbookSCMRepo
24
+
25
+ DIRTY_REPO = /^[\s]+M/
26
+
27
+ include Chef::Mixin::ShellOut
28
+
29
+ attr_reader :repo_path
30
+ attr_reader :default_branch
31
+ attr_reader :ui
32
+
33
+ def initialize(repo_path, ui, opts={})
34
+ @repo_path = repo_path
35
+ @ui = ui
36
+ @default_branch = 'master'
37
+ end
38
+
39
+ def sanity_check
40
+ unless ::File.directory?(repo_path)
41
+ ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory")
42
+ exit 1
43
+ end
44
+ unless git_repo?(repo_path)
45
+ ui.error "The cookbook repo #{repo_path} is not a git repository."
46
+ ui.info("Use `git init` to initialize a git repo")
47
+ exit 1
48
+ end
49
+ unless branch_exists?(default_branch)
50
+ ui.error "The default branch '#{default_branch}' does not exist"
51
+ ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
52
+ exit 1
53
+ end
54
+ cmd = git('status --porcelain')
55
+ if cmd.stdout =~ DIRTY_REPO
56
+ ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
57
+ ui.msg cmd.stdout
58
+ ui.info "Commit or stash your changes before importing cookbooks"
59
+ exit 1
60
+ end
61
+ # TODO: any untracked files in the cookbook directory will get nuked later
62
+ # make this an error condition also.
63
+ true
64
+ end
65
+
66
+ def reset_to_default_state
67
+ ui.info("Checking out the #{default_branch} branch.")
68
+ git("checkout #{default_branch}")
69
+ end
70
+
71
+ def prepare_to_import(cookbook_name)
72
+ branch = "chef-vendor-#{cookbook_name}"
73
+ if branch_exists?(branch)
74
+ ui.info("Pristine copy branch (#{branch}) exists, switching to it.")
75
+ git("checkout #{branch}")
76
+ else
77
+ ui.info("Creating pristine copy branch #{branch}")
78
+ git("checkout -b #{branch}")
79
+ end
80
+ end
81
+
82
+ def finalize_updates_to(cookbook_name, version)
83
+ if update_count = updated?(cookbook_name)
84
+ ui.info "#{update_count} files updated, committing changes"
85
+ git("add #{cookbook_name}")
86
+ git("commit -m 'Import #{cookbook_name} version #{version}' -- #{cookbook_name}")
87
+ ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}")
88
+ git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}")
89
+ true
90
+ else
91
+ ui.info("No changes made to #{cookbook_name}")
92
+ false
93
+ end
94
+ end
95
+
96
+ def merge_updates_from(cookbook_name, version)
97
+ branch = "chef-vendor-#{cookbook_name}"
98
+ Dir.chdir(repo_path) do
99
+ if system("git merge #{branch}")
100
+ ui.info("Cookbook #{cookbook_name} version #{version} successfully installed")
101
+ else
102
+ ui.error("You have merge conflicts - please resolve manually")
103
+ ui.info("Merge status (cd #{repo_path}; git status):")
104
+ system("git status")
105
+ exit 3
106
+ end
107
+ end
108
+ end
109
+
110
+ def updated?(cookbook_name)
111
+ update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count
112
+ update_count == 0 ? nil : update_count
113
+ end
114
+
115
+ def branch_exists?(branch_name)
116
+ git("branch --no-color").stdout.lines.any? {|l| l.include?(branch_name) }
117
+ end
118
+
119
+ private
120
+
121
+ def git_repo?(directory)
122
+ if File.directory?(File.join(directory, '.git'))
123
+ return true
124
+ elsif File.dirname(directory) == directory
125
+ return false
126
+ else
127
+ git_repo?(File.dirname(directory))
128
+ end
129
+ end
130
+
131
+ def apply_opts(opts)
132
+ opts.each do |option, value|
133
+ case option.to_s
134
+ when 'default_branch'
135
+ @default_branch = value
136
+ else
137
+ raise ArgumentError, "invalid option `#{option}' passed to CookbookRepo.new()"
138
+ end
139
+ end
140
+ end
141
+
142
+ def git(command)
143
+ shell_out!("git #{command}", :cwd => repo_path)
144
+ end
145
+
146
+ end
147
+ end
148
+ end
149
+
@@ -0,0 +1,184 @@
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 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/core/text_formatter'
20
+
21
+ class Chef
22
+ class Knife
23
+ module Core
24
+
25
+ #==Chef::Knife::Core::GenericPresenter
26
+ # The base presenter class for displaying structured data in knife commands.
27
+ # This is not an abstract base class, and it is suitable for displaying
28
+ # most kinds of objects that knife needs to display.
29
+ class GenericPresenter
30
+
31
+ attr_reader :ui
32
+ attr_reader :config
33
+
34
+ # Instaniates a new GenericPresenter. This is generally handled by the
35
+ # Chef::Knife::UI object, though you need to match the signature of this
36
+ # method if you intend to use your own presenter instead.
37
+ def initialize(ui, config)
38
+ @ui, @config = ui, config
39
+ end
40
+
41
+ # Is the selected output format a data interchange format?
42
+ # Returns true if the selected output format is json or yaml, false
43
+ # otherwise. Knife search uses this to adjust its data output so as not
44
+ # to produce invalid JSON output.
45
+ def interchange?
46
+ case parse_format_option
47
+ when :json, :yaml
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ # Returns a String representation of +data+ that is suitable for output
55
+ # to a terminal or perhaps for data interchange with another program.
56
+ # The representation of the +data+ depends on the value of the
57
+ # `config[:format]` setting.
58
+ def format(data)
59
+ case parse_format_option
60
+ when :summary
61
+ summarize(data)
62
+ when :text
63
+ text_format(data)
64
+ when :json
65
+ Chef::JSONCompat.to_json_pretty(data)
66
+ when :yaml
67
+ require 'yaml'
68
+ YAML::dump(data)
69
+ when :pp
70
+ # If you were looking for some attribute and there is only one match
71
+ # just dump the attribute value
72
+ if data.length == 1 and config[:attribute]
73
+ data.values[0]
74
+ else
75
+ out = StringIO.new
76
+ PP.pp(data, out)
77
+ out.string
78
+ end
79
+ end
80
+ end
81
+
82
+ # Converts the user-supplied value of `config[:format]` to a Symbol
83
+ # representing the desired output format.
84
+ # ===Returns
85
+ # returns one of :summary, :text, :json, :yaml, or :pp
86
+ # ===Raises
87
+ # Raises an ArgumentError if the desired output format could not be
88
+ # determined from the value of `config[:format]`
89
+ def parse_format_option
90
+ case config[:format]
91
+ when "summary", /^s/, nil
92
+ :summary
93
+ when "text", /^t/
94
+ :text
95
+ when "json", /^j/
96
+ :json
97
+ when "yaml", /^y/
98
+ :yaml
99
+ when "pp", /^p/
100
+ :pp
101
+ else
102
+ raise ArgumentError, "Unknown output format #{config[:format]}"
103
+ end
104
+ end
105
+
106
+ # Summarize the data. Defaults to text format output,
107
+ # which may not be very summary-like
108
+ def summarize(data)
109
+ text_format(data)
110
+ end
111
+
112
+ # Converts the +data+ to a String in the text format. Uses
113
+ # Chef::Knife::Core::TextFormatter
114
+ def text_format(data)
115
+ TextFormatter.new(data, ui).formatted_data
116
+ end
117
+
118
+ def format_list_for_display(list)
119
+ config[:with_uri] ? list : list.keys.sort { |a,b| a <=> b }
120
+ end
121
+
122
+ def format_for_display(data)
123
+ if config[:attribute]
124
+ result = {}
125
+ Array(config[:attribute]).each do |nested_value_spec|
126
+ nested_value = extract_nested_value(data, nested_value_spec)
127
+ result[nested_value_spec] = nested_value
128
+ end
129
+ result
130
+ elsif config[:run_list]
131
+ data = data.run_list.run_list
132
+ { "run_list" => data }
133
+ elsif config[:environment]
134
+ if data.respond_to?(:chef_environment)
135
+ {"chef_environment" => data.chef_environment}
136
+ else
137
+ # this is a place holder for now. Feel free to modify (i.e. add other cases). [nuo]
138
+ data
139
+ end
140
+ elsif config[:id_only]
141
+ data.respond_to?(:name) ? data.name : data["id"]
142
+ else
143
+ data
144
+ end
145
+ end
146
+
147
+ def extract_nested_value(data, nested_value_spec)
148
+ nested_value_spec.split(".").each do |attr|
149
+ if data.nil?
150
+ nil # don't get no method error on nil
151
+ elsif data.respond_to?(attr.to_sym)
152
+ data = data.send(attr.to_sym)
153
+ elsif data.respond_to?(:[])
154
+ data = data[attr]
155
+ else
156
+ data = begin
157
+ data.send(attr.to_sym)
158
+ rescue NoMethodError
159
+ nil
160
+ end
161
+ end
162
+ end
163
+ ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
164
+ end
165
+
166
+ def format_cookbook_list_for_display(item)
167
+ if config[:with_uri]
168
+ item
169
+ else
170
+ versions_by_cookbook = item.inject({}) do |collected, ( cookbook, versions )|
171
+ collected[cookbook] = versions["versions"].map {|v| v['version']}
172
+ collected
173
+ end
174
+ key_length = versions_by_cookbook.keys.map {|name| name.size }.max + 2
175
+ versions_by_cookbook.sort.map do |cookbook, versions|
176
+ "#{cookbook.ljust(key_length)} #{versions.join(',')}"
177
+ end
178
+ end
179
+ end
180
+
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,127 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 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/json_compat'
20
+ require 'chef/node'
21
+
22
+ class Chef
23
+ class Knife
24
+ class NodeEditor
25
+
26
+ attr_reader :node
27
+ attr_reader :ui
28
+ attr_reader :config
29
+
30
+ def initialize(node, ui, config)
31
+ @node, @ui, @config = node, ui, config
32
+ end
33
+
34
+ def edit_node
35
+ abort "You specified the --no-editor option, nothing to edit" if config[:no_editor]
36
+ assert_editor_set!
37
+
38
+ updated_node_data = edit_data(view)
39
+ apply_updates(updated_node_data)
40
+ @updated_node
41
+ end
42
+
43
+ def view
44
+ result = {}
45
+ result["name"] = node.name
46
+ result["chef_environment"] = node.chef_environment
47
+ result["normal"] = node.normal_attrs
48
+ result["run_list"] = node.run_list
49
+
50
+ if config[:all_attributes]
51
+ result["default"] = node.default_attrs
52
+ result["override"] = node.override_attrs
53
+ result["automatic"] = node.automatic_attrs
54
+ end
55
+ Chef::JSONCompat.to_json_pretty(result)
56
+ end
57
+
58
+ def edit_data(text)
59
+ edited_data = tempfile_for(text) {|filename| system("#{config[:editor]} #{filename}")}
60
+ Chef::JSONCompat.from_json(edited_data)
61
+ end
62
+
63
+ def apply_updates(updated_data)
64
+ # TODO: should warn/error/ask for confirmation when changing the
65
+ # name, since this results in a new node, not an edited node.
66
+ @updated_node = Node.new.tap do |n|
67
+ n.name( updated_data["name"] )
68
+ n.chef_environment( updated_data["chef_environment"] )
69
+ n.run_list( updated_data["run_list"])
70
+ n.normal_attrs = updated_data["normal"]
71
+
72
+ if config[:all_attributes]
73
+ n.default_attrs = updated_data["default"]
74
+ n.override_attrs = updated_data["override"]
75
+ n.automatic_attrs = updated_data["automatic"]
76
+ else
77
+ n.default_attrs = node.default_attrs
78
+ n.override_attrs = node.override_attrs
79
+ n.automatic_attrs = node.automatic_attrs
80
+ end
81
+ end
82
+ end
83
+
84
+ def updated?
85
+ pristine_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(node), :create_additions => false)
86
+ updated_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@updated_node), :create_additions => false)
87
+ unless pristine_copy == updated_copy
88
+ updated_properties = %w{name normal chef_environment run_list default override automatic}.reject do |key|
89
+ pristine_copy[key] == updated_copy[key]
90
+ end
91
+ end
92
+ ( pristine_copy != updated_copy ) && updated_properties
93
+ end
94
+
95
+ private
96
+
97
+ def abort(message)
98
+ ui.error(message)
99
+ exit 1
100
+ end
101
+
102
+ def assert_editor_set!
103
+ unless config[:editor]
104
+ abort "You must set your EDITOR environment variable or configure your editor via knife.rb"
105
+ end
106
+ end
107
+
108
+ def tempfile_for(data)
109
+ # TODO: include useful info like the node name in the temp file
110
+ # name
111
+ basename = "knife-edit-" << rand(1_000_000_000_000_000).to_s.rjust(15, '0') << '.js'
112
+ filename = File.join(Dir.tmpdir, basename)
113
+ File.open(filename, "w+") do |f|
114
+ f.sync = true
115
+ f.puts data
116
+ end
117
+
118
+ yield filename
119
+
120
+ IO.read(filename)
121
+ ensure
122
+ File.unlink(filename)
123
+ end
124
+ end
125
+ end
126
+ end
127
+