chef 0.9.18 → 0.10.0.beta.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +0 -3
- data/distro/arch/etc/rc.d/chef-server +0 -4
- data/distro/arch/etc/rc.d/chef-server-webui +0 -4
- data/distro/arch/etc/rc.d/chef-solr +0 -4
- data/distro/arch/etc/rc.d/chef-solr-indexer +0 -4
- data/lib/chef.rb +3 -3
- data/lib/chef/api_client.rb +1 -1
- data/lib/chef/application.rb +11 -1
- data/lib/chef/application/client.rb +18 -22
- data/lib/chef/application/knife.rb +28 -29
- data/lib/chef/application/solo.rb +14 -12
- data/lib/chef/client.rb +112 -54
- data/lib/chef/config.rb +4 -0
- data/lib/chef/cookbook/chefignore.rb +66 -0
- data/lib/chef/cookbook/cookbook_collection.rb +6 -5
- data/lib/chef/cookbook/cookbook_version_loader.rb +151 -0
- data/lib/chef/cookbook/file_system_file_vendor.rb +10 -8
- data/lib/chef/cookbook/metadata.rb +200 -108
- data/lib/chef/cookbook_loader.rb +39 -163
- data/lib/chef/cookbook_uploader.rb +100 -78
- data/lib/chef/cookbook_version.rb +92 -47
- data/lib/chef/cookbook_version_selector.rb +163 -0
- data/lib/chef/couchdb.rb +9 -1
- data/lib/chef/data_bag.rb +1 -1
- data/lib/chef/data_bag_item.rb +1 -1
- data/lib/chef/encrypted_data_bag_item.rb +126 -0
- data/lib/chef/environment.rb +386 -0
- data/lib/chef/exceptions.rb +82 -1
- data/lib/chef/index_queue/amqp_client.rb +15 -12
- data/lib/chef/index_queue/indexable.rb +38 -4
- data/lib/chef/json_compat.rb +3 -3
- data/lib/chef/knife.rb +97 -202
- data/lib/chef/knife/bootstrap.rb +27 -61
- data/lib/chef/knife/bootstrap/archlinux-gems.erb +4 -2
- data/lib/chef/knife/bootstrap/centos5-gems.erb +6 -15
- data/lib/chef/knife/bootstrap/fedora13-gems.erb +3 -4
- data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +2 -2
- data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +6 -5
- data/lib/chef/knife/client_bulk_delete.rb +6 -3
- data/lib/chef/knife/client_create.rb +13 -10
- data/lib/chef/knife/client_delete.rb +10 -7
- data/lib/chef/knife/client_edit.rb +9 -6
- data/lib/chef/knife/client_list.rb +8 -5
- data/lib/chef/knife/client_reregister.rb +9 -6
- data/lib/chef/knife/client_show.rb +9 -6
- data/lib/chef/knife/configure.rb +15 -19
- data/lib/chef/knife/configure_client.rb +4 -4
- data/lib/chef/knife/cookbook_bulk_delete.rb +11 -8
- data/lib/chef/knife/cookbook_create.rb +120 -55
- data/lib/chef/knife/cookbook_delete.rb +18 -12
- data/lib/chef/knife/cookbook_download.rb +10 -6
- data/lib/chef/knife/cookbook_list.rb +15 -6
- data/lib/chef/knife/cookbook_metadata.rb +41 -21
- data/lib/chef/knife/cookbook_metadata_from_file.rb +4 -0
- data/lib/chef/knife/cookbook_show.rb +16 -5
- data/lib/chef/knife/cookbook_site_download.rb +2 -2
- data/lib/chef/knife/cookbook_site_share.rb +18 -13
- data/lib/chef/knife/cookbook_site_unshare.rb +7 -4
- data/lib/chef/knife/cookbook_site_vendor.rb +21 -18
- data/lib/chef/knife/cookbook_test.rb +14 -14
- data/lib/chef/knife/cookbook_upload.rb +91 -40
- data/lib/chef/knife/data_bag_create.rb +41 -6
- data/lib/chef/knife/data_bag_delete.rb +5 -3
- data/lib/chef/knife/data_bag_edit.rb +55 -11
- data/lib/chef/knife/data_bag_from_file.rb +47 -7
- data/lib/chef/knife/data_bag_list.rb +4 -1
- data/lib/chef/knife/data_bag_show.rb +44 -4
- data/lib/chef/knife/environment_create.rb +53 -0
- data/lib/chef/knife/environment_delete.rb +45 -0
- data/lib/chef/knife/environment_edit.rb +45 -0
- data/lib/chef/knife/environment_from_file.rb +39 -0
- data/lib/chef/knife/environment_list.rb +42 -0
- data/lib/chef/knife/environment_show.rb +46 -0
- data/lib/chef/knife/exec.rb +1 -1
- data/lib/chef/knife/index_rebuild.rb +8 -9
- data/lib/chef/knife/node_bulk_delete.rb +9 -6
- data/lib/chef/knife/node_create.rb +9 -6
- data/lib/chef/knife/node_delete.rb +10 -7
- data/lib/chef/knife/node_edit.rb +129 -10
- data/lib/chef/knife/node_from_file.rb +10 -7
- data/lib/chef/knife/node_list.rb +11 -6
- data/lib/chef/knife/node_run_list_add.rb +10 -7
- data/lib/chef/knife/node_run_list_remove.rb +9 -6
- data/lib/chef/knife/node_show.rb +15 -7
- data/lib/chef/knife/recipe_list.rb +4 -3
- data/lib/chef/knife/role_bulk_delete.rb +9 -6
- data/lib/chef/knife/role_create.rb +9 -6
- data/lib/chef/knife/role_delete.rb +10 -7
- data/lib/chef/knife/role_edit.rb +11 -8
- data/lib/chef/knife/role_from_file.rb +10 -7
- data/lib/chef/knife/role_list.rb +8 -5
- data/lib/chef/knife/role_show.rb +11 -8
- data/lib/chef/knife/search.rb +33 -10
- data/lib/chef/knife/ssh.rb +33 -61
- data/lib/chef/knife/status.rb +7 -4
- data/lib/chef/knife/subcommand_loader.rb +101 -0
- data/lib/chef/knife/tag_create.rb +31 -0
- data/lib/chef/knife/tag_delete.rb +31 -0
- data/lib/chef/knife/tag_list.rb +29 -0
- data/lib/chef/knife/ui.rb +229 -0
- data/lib/chef/knife/windows_bootstrap.rb +8 -5
- data/lib/chef/log.rb +5 -59
- data/lib/chef/mash.rb +211 -0
- data/lib/chef/mixins.rb +1 -2
- data/lib/chef/nil_argument.rb +3 -0
- data/lib/chef/node.rb +96 -34
- data/lib/chef/platform.rb +27 -0
- data/lib/chef/provider/cookbook_file.rb +21 -20
- data/lib/chef/provider/deploy/revision.rb +3 -0
- data/lib/chef/provider/file.rb +20 -11
- data/lib/chef/provider/git.rb +26 -26
- data/lib/chef/provider/group/aix.rb +70 -0
- data/lib/chef/provider/group/groupadd.rb +7 -4
- data/lib/chef/provider/group/usermod.rb +1 -1
- data/lib/chef/provider/package.rb +28 -28
- data/lib/chef/provider/package/dpkg.rb +1 -1
- data/lib/chef/provider/package/portage.rb +50 -39
- data/lib/chef/provider/package/rubygems.rb +1 -1
- data/lib/chef/provider/package/zypper.rb +3 -20
- data/lib/chef/provider/remote_directory.rb +0 -2
- data/lib/chef/provider/remote_file.rb +2 -3
- data/lib/chef/provider/service/arch.rb +28 -35
- data/lib/chef/provider/service/simple.rb +1 -1
- data/lib/chef/provider/subversion.rb +22 -22
- data/lib/chef/providers.rb +1 -0
- data/lib/chef/recipe.rb +10 -12
- data/lib/chef/resource.rb +49 -42
- data/lib/chef/resource/gem_package.rb +7 -3
- data/lib/chef/resource/git.rb +5 -5
- data/lib/chef/resource/package.rb +7 -7
- data/lib/chef/resource/scm.rb +2 -1
- data/lib/chef/resource/solaris_package.rb +0 -1
- data/lib/chef/resource/yum_package.rb +0 -1
- data/lib/chef/rest.rb +7 -16
- data/lib/chef/rest/rest_request.rb +0 -16
- data/lib/chef/role.rb +67 -13
- data/lib/chef/run_context.rb +37 -21
- data/lib/chef/run_list.rb +30 -15
- data/lib/chef/run_list/run_list_expansion.rb +41 -20
- data/lib/chef/run_list/run_list_item.rb +20 -6
- data/lib/chef/run_list/versioned_recipe_list.rb +68 -0
- data/lib/chef/runner.rb +7 -15
- data/lib/chef/search/query.rb +12 -7
- data/lib/chef/shef.rb +6 -7
- data/lib/chef/shef/shef_session.rb +40 -35
- data/lib/chef/shell_out.rb +22 -201
- data/lib/chef/shell_out/unix.rb +224 -0
- data/lib/chef/shell_out/windows.rb +95 -0
- data/lib/chef/solr_query.rb +187 -0
- data/lib/chef/solr_query/lucene.treetop +145 -0
- data/lib/chef/solr_query/lucene_nodes.rb +285 -0
- data/lib/chef/solr_query/query_transform.rb +65 -0
- data/lib/chef/solr_query/solr_http_request.rb +118 -0
- data/lib/chef/version.rb +4 -2
- data/lib/chef/version_class.rb +70 -0
- data/lib/chef/version_constraint.rb +116 -0
- metadata +68 -37
- data/lib/chef/cookbook/metadata/version.rb +0 -87
- data/lib/chef/knife/bluebox_images_list.rb +0 -54
- data/lib/chef/knife/bluebox_server_create.rb +0 -157
- data/lib/chef/knife/bluebox_server_delete.rb +0 -63
- data/lib/chef/knife/bluebox_server_list.rb +0 -59
- data/lib/chef/knife/ec2_instance_data.rb +0 -46
- data/lib/chef/knife/ec2_server_create.rb +0 -218
- data/lib/chef/knife/ec2_server_delete.rb +0 -87
- data/lib/chef/knife/ec2_server_list.rb +0 -89
- data/lib/chef/knife/rackspace_server_create.rb +0 -184
- data/lib/chef/knife/rackspace_server_delete.rb +0 -57
- data/lib/chef/knife/rackspace_server_list.rb +0 -59
- data/lib/chef/knife/slicehost_images_list.rb +0 -53
- data/lib/chef/knife/slicehost_server_create.rb +0 -103
- data/lib/chef/knife/slicehost_server_delete.rb +0 -61
- data/lib/chef/knife/slicehost_server_list.rb +0 -64
- data/lib/chef/knife/terremark_server_create.rb +0 -152
- data/lib/chef/knife/terremark_server_delete.rb +0 -87
- data/lib/chef/knife/terremark_server_list.rb +0 -77
- data/lib/chef/mixin/find_preferred_file.rb +0 -92
data/lib/chef/exceptions.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
-
#
|
3
|
+
# Author:: Seth Falcon (<seth@opscode.com>)
|
4
|
+
# Copyright:: Copyright 2008-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");
|
@@ -49,6 +50,9 @@ class Chef
|
|
49
50
|
class RedirectLimitExceeded < RuntimeError; end
|
50
51
|
class AmbiguousRunlistSpecification < ArgumentError; end
|
51
52
|
class CookbookNotFound < RuntimeError; end
|
53
|
+
# Cookbook loader used to raise an argument error when cookbook not found.
|
54
|
+
# for back compat, need to raise an error that inherits from ArgumentError
|
55
|
+
class CookbookNotFoundInRepo < ArgumentError; end
|
52
56
|
class AttributeNotFound < RuntimeError; end
|
53
57
|
class InvalidCommandOption < RuntimeError; end
|
54
58
|
class CommandTimeout < RuntimeError; end
|
@@ -63,10 +67,87 @@ class Chef
|
|
63
67
|
class InvalidResourceSpecification < ArgumentError; end
|
64
68
|
class SolrConnectionError < RuntimeError; end
|
65
69
|
class IllegalChecksumRevert < RuntimeError; end
|
70
|
+
class CookbookVersionNameMismatch < ArgumentError; end
|
66
71
|
class MissingParentDirectory < RuntimeError; end
|
67
72
|
class UnresolvableGitReference < RuntimeError; end
|
68
73
|
class InvalidEnvironmentRunListSpecification < ArgumentError; end
|
69
74
|
class InvalidDataBagItemID < ArgumentError; end
|
70
75
|
class InvalidDataBagName < ArgumentError; end
|
76
|
+
class EnclosingDirectoryDoesNotExist < ArgumentError; end
|
77
|
+
|
78
|
+
class ObsoleteDependencySyntax < ArgumentError; end
|
79
|
+
|
80
|
+
# A different version of a cookbook was added to a
|
81
|
+
# VersionedRecipeList than the one already there.
|
82
|
+
class CookbookVersionConflict < ArgumentError ; end
|
83
|
+
|
84
|
+
# does not follow X.Y.Z format. ArgumentError?
|
85
|
+
class InvalidCookbookVersion < ArgumentError; end
|
86
|
+
|
87
|
+
# version constraint should be a string or array, or it doesn't
|
88
|
+
# match OP VERSION. ArgumentError?
|
89
|
+
class InvalidVersionConstraint < ArgumentError; end
|
90
|
+
|
91
|
+
class CookbookVersionSelection
|
92
|
+
|
93
|
+
# Compound exception: In run_list expansion and resolution,
|
94
|
+
# run_list items referred to cookbooks that don't exist and/or
|
95
|
+
# have no versions available.
|
96
|
+
class InvalidRunListItems < StandardError
|
97
|
+
attr_reader :non_existent_cookbooks
|
98
|
+
attr_reader :cookbooks_with_no_matching_versions
|
99
|
+
|
100
|
+
def initialize(message, non_existent_cookbooks, cookbooks_with_no_matching_versions)
|
101
|
+
super(message)
|
102
|
+
|
103
|
+
@non_existent_cookbooks = non_existent_cookbooks
|
104
|
+
@cookbooks_with_no_matching_versions = cookbooks_with_no_matching_versions
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_json(*a)
|
108
|
+
result = {
|
109
|
+
"message" => message,
|
110
|
+
"non_existent_cookbooks" => non_existent_cookbooks,
|
111
|
+
"cookbooks_with_no_versions" => cookbooks_with_no_matching_versions
|
112
|
+
}
|
113
|
+
result.to_json(*a)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# In run_list expansion and resolution, a constraint was
|
118
|
+
# unsatisfiable.
|
119
|
+
#
|
120
|
+
# This exception may not be the complete error report. If you
|
121
|
+
# resolve the misconfiguration represented by this exception and
|
122
|
+
# re-solve, you may get another exception
|
123
|
+
class UnsatisfiableRunListItem < StandardError
|
124
|
+
attr_reader :run_list_item
|
125
|
+
attr_reader :non_existent_cookbooks, :most_constrained_cookbooks
|
126
|
+
|
127
|
+
# most_constrained_cookbooks: if I were to remove constraints
|
128
|
+
# regarding these cookbooks, I would get a solution or move on
|
129
|
+
# to the next error (deeper in the graph). An item in this list
|
130
|
+
# may be unsatisfiable, but when resolved may also reveal
|
131
|
+
# further unsatisfiable constraints; this condition would not be
|
132
|
+
# reported.
|
133
|
+
def initialize(message, run_list_item, non_existent_cookbooks, most_constrained_cookbooks)
|
134
|
+
super(message)
|
135
|
+
|
136
|
+
@run_list_item = run_list_item
|
137
|
+
@non_existent_cookbooks = non_existent_cookbooks
|
138
|
+
@most_constrained_cookbooks = most_constrained_cookbooks
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_json(*a)
|
142
|
+
result = {
|
143
|
+
"message" => message,
|
144
|
+
"unsatisfiable_run_list_item" => run_list_item,
|
145
|
+
"non_existent_cookbooks" => non_existent_cookbooks,
|
146
|
+
"most_constrained_cookbooks" => most_constrained_cookbooks
|
147
|
+
}
|
148
|
+
result.to_json(*a)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
71
152
|
end
|
72
153
|
end
|
@@ -19,6 +19,8 @@
|
|
19
19
|
class Chef
|
20
20
|
module IndexQueue
|
21
21
|
class AmqpClient
|
22
|
+
VNODES = 1024
|
23
|
+
|
22
24
|
include Singleton
|
23
25
|
|
24
26
|
def initialize
|
@@ -29,11 +31,9 @@ class Chef
|
|
29
31
|
@amqp_client && amqp_client.connected? && amqp_client.stop
|
30
32
|
@amqp_client = nil
|
31
33
|
@exchange = nil
|
32
|
-
@queue = nil
|
33
34
|
end
|
34
35
|
|
35
36
|
def stop
|
36
|
-
@queue && @queue.subscription && @queue.unsubscribe
|
37
37
|
@amqp_client && @amqp_client.stop
|
38
38
|
end
|
39
39
|
|
@@ -59,24 +59,17 @@ class Chef
|
|
59
59
|
@exchange ||= amqp_client.exchange("chef-indexer", :durable => true, :type => :fanout)
|
60
60
|
end
|
61
61
|
|
62
|
-
def queue
|
63
|
-
unless @queue
|
64
|
-
@queue = amqp_client.queue("chef-index-consumer-" + consumer_id, :durable => durable_queue?)
|
65
|
-
@queue.bind(exchange)
|
66
|
-
end
|
67
|
-
@queue
|
68
|
-
end
|
69
|
-
|
70
62
|
def disconnected!
|
71
63
|
Chef::Log.error("Disconnected from the AMQP Broker (RabbitMQ)")
|
72
64
|
@amqp_client = nil
|
73
65
|
reset!
|
74
66
|
end
|
75
67
|
|
76
|
-
def
|
68
|
+
def queue_for_object(obj_id)
|
77
69
|
retries = 0
|
70
|
+
vnode_tag = obj_id_to_int(obj_id) % VNODES
|
78
71
|
begin
|
79
|
-
|
72
|
+
yield amqp_client.queue("vnode-#{vnode_tag}", :passive => false, :durable => true, :exclusive => false, :auto_delete => false)
|
80
73
|
rescue Bunny::ServerDownError, Bunny::ConnectionError, Errno::ECONNRESET
|
81
74
|
disconnected!
|
82
75
|
if (retries += 1) < 2
|
@@ -90,6 +83,15 @@ class Chef
|
|
90
83
|
end
|
91
84
|
|
92
85
|
private
|
86
|
+
|
87
|
+
# Sometimes object ids are "proper" UUIDs, like "64bc00eb-120b-b6a2-ec0e-34fc90d151be"
|
88
|
+
# and sometimes they omit the dashes, like "64bc00eb120bb6a2ec0e34fc90d151be"
|
89
|
+
# UUIDTools uses different methods to parse the different styles.
|
90
|
+
def obj_id_to_int(obj_id)
|
91
|
+
UUIDTools::UUID.parse(obj_id).to_i
|
92
|
+
rescue ArgumentError
|
93
|
+
UUIDTools::UUID.parse_hexdigest(obj_id).to_i
|
94
|
+
end
|
93
95
|
|
94
96
|
def durable_queue?
|
95
97
|
!!Chef::Config[:amqp_consumer_id]
|
@@ -111,3 +113,4 @@ class Chef
|
|
111
113
|
end
|
112
114
|
end
|
113
115
|
end
|
116
|
+
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Daniel DeLeo (<dan@kallistec.com>)
|
3
|
+
# Author:: Seth Falcon (<seth@opscode.com>)
|
3
4
|
# Copyright:: Copyright (c) 2009 Daniel DeLeo
|
5
|
+
# Copyright:: Copyright (c) 2010 Opscode, Inc.
|
4
6
|
# License:: Apache License, Version 2.0
|
5
7
|
#
|
6
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -15,6 +17,7 @@
|
|
15
17
|
# See the License for the specific language governing permissions and
|
16
18
|
# limitations under the License.
|
17
19
|
#
|
20
|
+
require 'chef/json_compat'
|
18
21
|
|
19
22
|
class Chef
|
20
23
|
module IndexQueue
|
@@ -56,20 +59,51 @@ class Chef
|
|
56
59
|
with_metadata["id"] ||= self.index_id
|
57
60
|
with_metadata["database"] ||= Chef::Config[:couchdb_database]
|
58
61
|
with_metadata["item"] ||= self.to_hash
|
62
|
+
with_metadata["enqueued_at"] ||= Time.now.utc.to_i
|
59
63
|
|
60
64
|
raise ArgumentError, "Type, Id, or Database missing in index operation: #{with_metadata.inspect}" if (with_metadata["id"].nil? or with_metadata["type"].nil?)
|
61
65
|
with_metadata
|
62
66
|
end
|
63
|
-
|
67
|
+
|
64
68
|
def add_to_index(metadata={})
|
65
|
-
|
66
|
-
|
69
|
+
Chef::Log.debug("pushing item to index queue for addition: #{self.with_indexer_metadata(metadata)}")
|
70
|
+
object_with_metadata = with_indexer_metadata(metadata)
|
71
|
+
obj_id = object_with_metadata["id"]
|
72
|
+
obj = {:action => :add, :payload => self.with_indexer_metadata(metadata)}
|
73
|
+
|
74
|
+
publish_object(obj_id, obj)
|
67
75
|
end
|
68
76
|
|
69
77
|
def delete_from_index(metadata={})
|
70
78
|
Chef::Log.debug("pushing item to index queue for deletion: #{self.with_indexer_metadata(metadata)}")
|
71
|
-
|
79
|
+
object_with_metadata = with_indexer_metadata(metadata)
|
80
|
+
obj_id = object_with_metadata["id"]
|
81
|
+
obj = {:action => :delete, :payload => self.with_indexer_metadata(metadata)}
|
82
|
+
|
83
|
+
publish_object(obj_id, obj)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Uses the publisher to update the object's queue. If
|
89
|
+
# Chef::Config[:persistent_queue] is true, the update is wrapped
|
90
|
+
# in a transaction.
|
91
|
+
def publish_object(object_id, object)
|
92
|
+
publisher = AmqpClient.instance
|
93
|
+
begin
|
94
|
+
publisher.amqp_client.tx_select if Chef::Config[:persistent_queue]
|
95
|
+
publisher.queue_for_object(object_id) do |queue|
|
96
|
+
queue.publish(Chef::JSONCompat.to_json(object), :persistent => Chef::Config[:persistent_queue])
|
97
|
+
end
|
98
|
+
publisher.amqp_client.tx_commit if Chef::Config[:persistent_queue]
|
99
|
+
rescue
|
100
|
+
publisher.amqp_client.tx_rollback if Chef::Config[:persistent_queue]
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
72
105
|
end
|
106
|
+
|
73
107
|
end
|
74
108
|
end
|
75
109
|
end
|
data/lib/chef/json_compat.rb
CHANGED
@@ -24,8 +24,9 @@ class Chef
|
|
24
24
|
JSON_MAX_NESTING = 1000
|
25
25
|
|
26
26
|
class <<self
|
27
|
-
# See PL-538. Increase the max nesting for JSON, which defaults
|
28
|
-
# and isn't enough for some
|
27
|
+
# See CHEF-1292/PL-538. Increase the max nesting for JSON, which defaults
|
28
|
+
# to 19, and isn't enough for some (for example, a Node within a Node)
|
29
|
+
# structures.
|
29
30
|
def opts_add_max_nesting(opts)
|
30
31
|
if opts.nil? || !opts.has_key?(:max_nesting)
|
31
32
|
opts = opts.nil? ? Hash.new : opts.clone
|
@@ -40,7 +41,6 @@ class Chef
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def to_json(obj, opts = nil)
|
43
|
-
#::JSON.generate(obj, opts_add_max_nesting(opts))
|
44
44
|
obj.to_json(opts_add_max_nesting(opts))
|
45
45
|
end
|
46
46
|
|
data/lib/chef/knife.rb
CHANGED
@@ -7,9 +7,9 @@
|
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
15
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@@ -17,28 +17,52 @@
|
|
17
17
|
# limitations under the License.
|
18
18
|
#
|
19
19
|
|
20
|
+
require 'forwardable'
|
20
21
|
require 'chef/version'
|
21
22
|
require 'mixlib/cli'
|
22
23
|
require 'chef/mixin/convert_to_class_name'
|
23
|
-
require 'chef/
|
24
|
+
require 'chef/knife/subcommand_loader'
|
25
|
+
require 'chef/knife/ui'
|
26
|
+
|
24
27
|
require 'pp'
|
25
28
|
|
26
29
|
class Chef
|
27
30
|
class Knife
|
28
|
-
|
29
|
-
Chef::REST::RESTRequest.user_agent = "Chef Knife#{Chef::REST::RESTRequest::UA_COMMON}"
|
30
|
-
|
31
31
|
include Mixlib::CLI
|
32
32
|
extend Chef::Mixin::ConvertToClassName
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
extend Forwardable
|
34
|
+
|
35
|
+
# Backwards Compat:
|
36
|
+
# Ideally, we should not vomit all of these methods into this base class;
|
37
|
+
# instead, they should be accessed by hitting the ui object directly.
|
38
|
+
def_delegator :@ui, :stdout
|
39
|
+
def_delegator :@ui, :stderr
|
40
|
+
def_delegator :@ui, :stdin
|
41
|
+
def_delegator :@ui, :msg
|
42
|
+
def_delegator :@ui, :ask_question
|
43
|
+
def_delegator :@ui, :pretty_print
|
44
|
+
def_delegator :@ui, :output
|
45
|
+
def_delegator :@ui, :format_list_for_display
|
46
|
+
def_delegator :@ui, :format_for_display
|
47
|
+
def_delegator :@ui, :format_cookbook_list_for_display
|
48
|
+
def_delegator :@ui, :edit_data
|
49
|
+
def_delegator :@ui, :edit_object
|
50
|
+
def_delegator :@ui, :confirm
|
37
51
|
|
38
52
|
attr_accessor :name_args
|
53
|
+
attr_reader :ui
|
54
|
+
|
55
|
+
def self.ui
|
56
|
+
@ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
|
57
|
+
end
|
39
58
|
|
40
59
|
def self.msg(msg="")
|
41
|
-
|
60
|
+
ui.msg(msg)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.reset_subcommands!
|
64
|
+
@@subcommands = {}
|
65
|
+
@subcommands_by_category = nil
|
42
66
|
end
|
43
67
|
|
44
68
|
def self.inherited(subclass)
|
@@ -73,6 +97,14 @@ class Chef
|
|
73
97
|
name.nil? || name.empty?
|
74
98
|
end
|
75
99
|
|
100
|
+
def self.subcommand_loader
|
101
|
+
@subcommand_loader ||= Knife::SubcommandLoader.new(chef_config_dir)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.load_commands
|
105
|
+
subcommand_loader.load_commands
|
106
|
+
end
|
107
|
+
|
76
108
|
def self.subcommands
|
77
109
|
@@subcommands ||= {}
|
78
110
|
end
|
@@ -87,17 +119,10 @@ class Chef
|
|
87
119
|
@subcommands_by_category
|
88
120
|
end
|
89
121
|
|
90
|
-
# Load all the sub-commands
|
91
|
-
def self.load_commands
|
92
|
-
DEFAULT_SUBCOMMAND_FILES.each { |subcommand| require subcommand }
|
93
|
-
subcommands
|
94
|
-
end
|
95
|
-
|
96
122
|
# Print the list of subcommands knife knows about. If +preferred_category+
|
97
123
|
# is given, only subcommands in that category are shown
|
98
124
|
def self.list_commands(preferred_category=nil)
|
99
125
|
load_commands
|
100
|
-
|
101
126
|
category_desc = preferred_category ? preferred_category + " " : ''
|
102
127
|
msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
|
103
128
|
|
@@ -125,7 +150,8 @@ class Chef
|
|
125
150
|
def self.run(args, options={})
|
126
151
|
load_commands
|
127
152
|
subcommand_class = subcommand_class_from(args)
|
128
|
-
subcommand_class.options.merge!(options)
|
153
|
+
subcommand_class.options = options.merge!(subcommand_class.options)
|
154
|
+
subcommand_class.load_deps
|
129
155
|
instance = subcommand_class.new(args)
|
130
156
|
instance.configure_chef
|
131
157
|
instance.run
|
@@ -155,16 +181,12 @@ class Chef
|
|
155
181
|
subcommand_class || subcommand_not_found!(args)
|
156
182
|
end
|
157
183
|
|
158
|
-
|
184
|
+
def self.deps(&block)
|
185
|
+
@dependency_loader = block
|
186
|
+
end
|
159
187
|
|
160
|
-
def
|
161
|
-
|
162
|
-
require dep
|
163
|
-
rescue LoadError
|
164
|
-
gem_name ||= dep.gsub('/', '-')
|
165
|
-
Chef::Log.fatal "#{gem_name} is not installed. run \"gem install #{gem_name}\" to install it."
|
166
|
-
exit 1
|
167
|
-
end
|
188
|
+
def self.load_deps
|
189
|
+
@dependency_loader && @dependency_loader.call
|
168
190
|
end
|
169
191
|
|
170
192
|
private
|
@@ -174,9 +196,9 @@ class Chef
|
|
174
196
|
# user could not be resolved to a subcommand.
|
175
197
|
def self.subcommand_not_found!(args)
|
176
198
|
unless want_help?(args)
|
177
|
-
|
199
|
+
ui.fatal("Cannot find sub command for: '#{args.join(' ')}'")
|
178
200
|
end
|
179
|
-
|
201
|
+
list_commands(guess_category(args))
|
180
202
|
exit 10
|
181
203
|
end
|
182
204
|
|
@@ -187,12 +209,32 @@ class Chef
|
|
187
209
|
(args.any? { |arg| arg =~ /^(:?(:?\-\-)?help|\-h)$/})
|
188
210
|
end
|
189
211
|
|
212
|
+
@@chef_config_dir = nil
|
213
|
+
|
214
|
+
# search upward from current_dir until .chef directory is found
|
215
|
+
def self.chef_config_dir
|
216
|
+
if @@chef_config_dir.nil? # share this with subclasses
|
217
|
+
@@chef_config_dir = false
|
218
|
+
full_path = Dir.pwd.split(File::SEPARATOR)
|
219
|
+
(full_path.length - 1).downto(0) do |i|
|
220
|
+
canidate_directory = File.join(full_path[0..i] + [".chef" ])
|
221
|
+
if File.exist?(canidate_directory) && File.directory?(canidate_directory)
|
222
|
+
@@chef_config_dir = canidate_directory
|
223
|
+
break
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
@@chef_config_dir
|
228
|
+
end
|
229
|
+
|
230
|
+
|
190
231
|
public
|
191
232
|
|
192
233
|
# Create a new instance of the current class configured for the given
|
193
234
|
# arguments and options
|
194
235
|
def initialize(argv=[])
|
195
236
|
super() # having to call super in initialize is the most annoying anti-pattern :(
|
237
|
+
@ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, config)
|
196
238
|
|
197
239
|
command_name_words = self.class.snake_case_name.split('_')
|
198
240
|
|
@@ -219,37 +261,11 @@ class Chef
|
|
219
261
|
exit(1)
|
220
262
|
end
|
221
263
|
|
222
|
-
def ask_question(question, opts={})
|
223
|
-
question = question + "[#{opts[:default]}] " if opts[:default]
|
224
|
-
|
225
|
-
if opts[:default] and config[:defaults]
|
226
|
-
|
227
|
-
opts[:default]
|
228
|
-
|
229
|
-
else
|
230
|
-
|
231
|
-
stdout.print question
|
232
|
-
a = stdin.readline.strip
|
233
|
-
|
234
|
-
if opts[:default]
|
235
|
-
a.empty? ? opts[:default] : a
|
236
|
-
else
|
237
|
-
a
|
238
|
-
end
|
239
|
-
|
240
|
-
end
|
241
|
-
|
242
|
-
end
|
243
|
-
|
244
264
|
def configure_chef
|
245
265
|
unless config[:config_file]
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
if File.exists?(config_file_to_check)
|
250
|
-
config[:config_file] = config_file_to_check
|
251
|
-
break
|
252
|
-
end
|
266
|
+
if self.class.chef_config_dir
|
267
|
+
candidate_config = File.expand_path('knife.rb',self.class.chef_config_dir)
|
268
|
+
config[:config_file] = candidate_config if File.exist?(candidate_config)
|
253
269
|
end
|
254
270
|
# If we haven't set a config yet and $HOME is set, and the home
|
255
271
|
# knife.rb exists, use it:
|
@@ -266,13 +282,22 @@ class Chef
|
|
266
282
|
self.msg("No knife configuration file found")
|
267
283
|
end
|
268
284
|
|
269
|
-
Chef::Config[:log_level]
|
270
|
-
Chef::Config[:log_location]
|
271
|
-
Chef::Config[:node_name]
|
272
|
-
Chef::Config[:client_key]
|
273
|
-
Chef::Config[:chef_server_url]
|
285
|
+
Chef::Config[:log_level] = config[:log_level] if config[:log_level]
|
286
|
+
Chef::Config[:log_location] = config[:log_location] if config[:log_location]
|
287
|
+
Chef::Config[:node_name] = config[:node_name] if config[:node_name]
|
288
|
+
Chef::Config[:client_key] = config[:client_key] if config[:client_key]
|
289
|
+
Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
|
290
|
+
Chef::Config[:environment] = config[:environment] if config[:environment]
|
291
|
+
|
292
|
+
# Expand a relative path from the config directory. Config from command
|
293
|
+
# line should already be expanded, and absolute paths will be unchanged.
|
294
|
+
if Chef::Config[:client_key] && config[:config_file]
|
295
|
+
Chef::Config[:client_key] = File.expand_path(Chef::Config[:client_key], File.dirname(config[:config_file]))
|
296
|
+
end
|
297
|
+
|
298
|
+
Mixlib::Log::Formatter.show_time = false
|
274
299
|
Chef::Log.init(Chef::Config[:log_location])
|
275
|
-
Chef::Log.level(Chef::Config[:log_level])
|
300
|
+
Chef::Log.level(Chef::Config[:log_level] || :error)
|
276
301
|
|
277
302
|
Chef::Log.debug("Using configuration from #{config[:config_file]}")
|
278
303
|
|
@@ -281,99 +306,6 @@ class Chef
|
|
281
306
|
end
|
282
307
|
end
|
283
308
|
|
284
|
-
def pretty_print(data)
|
285
|
-
puts data
|
286
|
-
end
|
287
|
-
|
288
|
-
def output(data)
|
289
|
-
case config[:format]
|
290
|
-
when "json", nil
|
291
|
-
stdout.puts Chef::JSONCompat.to_json_pretty(data)
|
292
|
-
when "yaml"
|
293
|
-
require 'yaml'
|
294
|
-
stdout.puts YAML::dump(data)
|
295
|
-
when "text"
|
296
|
-
# If you were looking for some attribute and there is only one match
|
297
|
-
# just dump the attribute value
|
298
|
-
if data.length == 1 and config[:attribute]
|
299
|
-
stdout.puts data.values[0]
|
300
|
-
else
|
301
|
-
PP.pp(data, stdout)
|
302
|
-
end
|
303
|
-
else
|
304
|
-
raise ArgumentError, "Unknown output format #{config[:format]}"
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
def format_list_for_display(list)
|
309
|
-
config[:with_uri] ? list : list.keys.sort { |a,b| a <=> b }
|
310
|
-
end
|
311
|
-
|
312
|
-
def format_for_display(item)
|
313
|
-
data = item.kind_of?(Chef::DataBagItem) ? item.raw_data : item
|
314
|
-
|
315
|
-
if config[:attribute]
|
316
|
-
config[:attribute].split(".").each do |attr|
|
317
|
-
if data.respond_to?(:[])
|
318
|
-
data = data[attr]
|
319
|
-
elsif data.nil?
|
320
|
-
nil # don't get no method error on nil
|
321
|
-
else data.respond_to?(attr.to_sym)
|
322
|
-
data = data.send(attr.to_sym)
|
323
|
-
end
|
324
|
-
end
|
325
|
-
{ config[:attribute] => data.kind_of?(Chef::Node::Attribute) ? data.to_hash : data }
|
326
|
-
elsif config[:run_list]
|
327
|
-
data = data.run_list.run_list
|
328
|
-
{ "run_list" => data }
|
329
|
-
elsif config[:id_only]
|
330
|
-
data.respond_to?(:name) ? data.name : data["id"]
|
331
|
-
else
|
332
|
-
data
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
def edit_data(data, parse_output=true)
|
337
|
-
output = Chef::JSONCompat.to_json_pretty(data)
|
338
|
-
|
339
|
-
if (!config[:no_editor])
|
340
|
-
filename = "knife-edit-"
|
341
|
-
0.upto(20) { filename += rand(9).to_s }
|
342
|
-
filename << ".js"
|
343
|
-
filename = File.join(Dir.tmpdir, filename)
|
344
|
-
tf = File.open(filename, "w")
|
345
|
-
tf.sync = true
|
346
|
-
tf.puts output
|
347
|
-
tf.close
|
348
|
-
raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}")
|
349
|
-
tf = File.open(filename, "r")
|
350
|
-
output = tf.gets(nil)
|
351
|
-
tf.close
|
352
|
-
File.unlink(filename)
|
353
|
-
end
|
354
|
-
|
355
|
-
parse_output ? Chef::JSONCompat.from_json(output) : output
|
356
|
-
end
|
357
|
-
|
358
|
-
def confirm(question, append_instructions=true)
|
359
|
-
return true if config[:yes]
|
360
|
-
|
361
|
-
stdout.print question
|
362
|
-
stdout.print "? (Y/N) " if append_instructions
|
363
|
-
answer = stdin.readline
|
364
|
-
answer.chomp!
|
365
|
-
case answer
|
366
|
-
when "Y", "y"
|
367
|
-
true
|
368
|
-
when "N", "n"
|
369
|
-
self.msg("You said no, so I'm done here.")
|
370
|
-
exit 3
|
371
|
-
else
|
372
|
-
self.msg("I have no idea what to do with #{answer}")
|
373
|
-
self.msg("Just say Y or N, please.")
|
374
|
-
confirm(question)
|
375
|
-
end
|
376
|
-
end
|
377
309
|
|
378
310
|
def show_usage
|
379
311
|
stdout.puts("USAGE: " + self.opt_parser.to_s)
|
@@ -387,6 +319,8 @@ class Chef
|
|
387
319
|
relative_path = "nodes"
|
388
320
|
elsif klass == Chef::DataBagItem
|
389
321
|
relative_path = "data_bags/#{bag}"
|
322
|
+
elsif klass == Chef::Environment
|
323
|
+
relative_path = "environments"
|
390
324
|
end
|
391
325
|
|
392
326
|
relative_file = File.expand_path(File.join(Dir.pwd, relative_path, from_file))
|
@@ -394,10 +328,10 @@ class Chef
|
|
394
328
|
|
395
329
|
if file_exists_and_is_readable?(from_file)
|
396
330
|
filename = from_file
|
397
|
-
elsif file_exists_and_is_readable?(relative_file)
|
398
|
-
filename = relative_file
|
331
|
+
elsif file_exists_and_is_readable?(relative_file)
|
332
|
+
filename = relative_file
|
399
333
|
else
|
400
|
-
|
334
|
+
ui.fatal("Cannot find file #{from_file}")
|
401
335
|
exit 30
|
402
336
|
end
|
403
337
|
|
@@ -409,7 +343,7 @@ class Chef
|
|
409
343
|
r.from_file(filename)
|
410
344
|
r
|
411
345
|
else
|
412
|
-
|
346
|
+
ui.fatal("File must end in .js, .json, or .rb")
|
413
347
|
exit 30
|
414
348
|
end
|
415
349
|
end
|
@@ -418,33 +352,6 @@ class Chef
|
|
418
352
|
File.exists?(file) && File.readable?(file)
|
419
353
|
end
|
420
354
|
|
421
|
-
def edit_object(klass, name)
|
422
|
-
object = klass.load(name)
|
423
|
-
|
424
|
-
output = edit_data(object)
|
425
|
-
|
426
|
-
# Only make the save if the user changed the object.
|
427
|
-
#
|
428
|
-
# Output JSON for the original (object) and edited (output), then parse
|
429
|
-
# them without reconstituting the objects into real classes
|
430
|
-
# (create_additions=false). Then, compare the resulting simple objects,
|
431
|
-
# which will be Array/Hash/String/etc.
|
432
|
-
#
|
433
|
-
# We wouldn't have to do these shenanigans if all the editable objects
|
434
|
-
# implemented to_hash, or if to_json against a hash returned a string
|
435
|
-
# with stable key order.
|
436
|
-
object_parsed_again = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(object), :create_additions => false)
|
437
|
-
output_parsed_again = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(output), :create_additions => false)
|
438
|
-
if object_parsed_again != output_parsed_again
|
439
|
-
output.save
|
440
|
-
self.msg("Saved #{output}")
|
441
|
-
else
|
442
|
-
self.msg("Object unchanged, not saving")
|
443
|
-
end
|
444
|
-
|
445
|
-
output(format_for_display(object)) if config[:print_after]
|
446
|
-
end
|
447
|
-
|
448
355
|
def create_object(object, pretty_name=nil, &block)
|
449
356
|
output = edit_data(object)
|
450
357
|
|
@@ -457,7 +364,7 @@ class Chef
|
|
457
364
|
pretty_name ||= output
|
458
365
|
|
459
366
|
self.msg("Created (or updated) #{pretty_name}")
|
460
|
-
|
367
|
+
|
461
368
|
output(output) if config[:print_after]
|
462
369
|
end
|
463
370
|
|
@@ -505,18 +412,6 @@ class Chef
|
|
505
412
|
end
|
506
413
|
end
|
507
414
|
|
508
|
-
def msg(message)
|
509
|
-
stdout.puts message
|
510
|
-
end
|
511
|
-
|
512
|
-
def stdout
|
513
|
-
STDOUT
|
514
|
-
end
|
515
|
-
|
516
|
-
def stdin
|
517
|
-
STDIN
|
518
|
-
end
|
519
|
-
|
520
415
|
def rest
|
521
416
|
@rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
|
522
417
|
end
|