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
@@ -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
|
-
|
50
|
-
|
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
|
+
|