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