knife-audit 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +28 -4
- data/knife-audit.gemspec +2 -2
- data/lib/chef/knife/audit.rb +126 -14
- data/lib/chef/knife/knife_audit_cookbook/README.rdoc +8 -0
- data/lib/chef/knife/knife_audit_cookbook/metadata.rb +6 -0
- data/lib/chef/knife/knife_audit_cookbook/recipes/default.rb +32 -0
- data/lib/knife-audit/version.rb +1 -1
- metadata +9 -6
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
knife-audit
|
2
2
|
========
|
3
3
|
A knife plugin for determining which cookbooks are in use on which nodes of your Chef server or Opscode organization.
|
4
|
-
Allows you to safely maintain a chef cookbook set by determining which cookbooks are currently in use by nodes
|
4
|
+
Allows you to safely maintain a chef cookbook set by determining which cookbooks are currently in use by nodes - either solely via inclusion in node runlists (available from every node) or via runlist *or* include_recipes (for nodes with knife_audit helper cookbook installed).
|
5
5
|
|
6
6
|
|
7
7
|
Installing knife-audit
|
@@ -9,25 +9,47 @@ Installing knife-audit
|
|
9
9
|
|
10
10
|
#### Script install
|
11
11
|
|
12
|
-
Copy the knife-audit script from https://github.com/jbz/knife-audit/blob/master/lib/chef/knife/audit.rb to your .chef/plugins/knife directory.
|
12
|
+
Copy the knife-audit script from https://github.com/jbz/knife-audit/blob/master/lib/chef/knife/audit.rb to your .chef/plugins/knife directory. Note that script-installed knife audit will be unable to install the knife_audit helper cookbook for you.
|
13
13
|
|
14
|
+
#### Gem install
|
15
|
+
|
16
|
+
knife-audit is available on rubygems.org - if you have that source in your gemrc, you can simply use:
|
17
|
+
|
18
|
+
gem install knife-audit
|
19
|
+
|
20
|
+
...if you don't have internet access or just want a local gemfile, you can clone the repo and build/install a working gem from the main repo directory:
|
21
|
+
|
22
|
+
gem build knife-audit.gemspec
|
23
|
+
gem install ./knife-audit-<version>.gem
|
14
24
|
|
15
25
|
Usage
|
16
26
|
---------------
|
17
27
|
|
18
|
-
knife audit <COOKBOOK COOKBOOK ...>
|
28
|
+
knife audit [-a|-t] [-s] [-i] <COOKBOOK COOKBOOK ...>
|
19
29
|
|
20
30
|
If no cookbooks are specified, knife-audit will return a list of *all* cookbooks available on the currently configured Chef server or Opscode Platform organization, along with a count for each of how many nodes in the current Chef server or Opscode Platform organization explicitly reference that cookbook in their expanded runlist.
|
21
31
|
|
22
|
-
Note that this does *not* include nodes that call the cookbook via 'include' and/or 'depends' statements. The 'complete runlist' for nodes, which includes all cookbooks pulled in due to includes, is kept in Node.run_state.seen_recipes
|
32
|
+
Note that this does *not* include nodes that call the cookbook via 'include' and/or 'depends' statements. The 'complete runlist' for nodes, which includes all cookbooks pulled in due to includes, is kept in Node.run_state.seen_recipes, but this is an ephemeral attribute and is only populated locally on the node during a client run. It is not saved to the Chef server, therefore knife-audit cannot 'see' it 'unless' you have installed the knife_audit helper cookbook to your nodes (see 'Helper Cookbook' below).
|
23
33
|
|
24
34
|
If one or more cookbook names are specified on the command line, knife-audit will return a list of only those cookbooks and their counts. Specifying a cookbook which is not available on the Chef server will result in an error.
|
25
35
|
|
36
|
+
The '-a' or '--all-cookbooks' option will cause knife-audit to check on each node for the attribute [:knife_audit][:seen_recipes] (which the helper cookbook saves there). If it is present, it will use the contents of that attribute to determine which recipes the node is calling. If it is not present, it will fall back (for that node) to the regular expanded runlist. The output of knife-audit will, in this case, be in two parts: The first will be identical to the default output and will display totals for all those nodes which do *not* have the seen_recipes attribute available. The second part will be totals for all those nodes which *do* have the attribute available. They are separated so that if the '-s' option is used, the nodes can be differentiated.
|
37
|
+
|
38
|
+
The '-t' or '--totals' option will cause knife-audit to present a single output section containing the merged totals of all nodes with and without the helper cookbook. This is less accurate, but still useful and easier to read.
|
39
|
+
|
26
40
|
The '-s' or '--show-nodelist' option will cause knife-audit to include in its output a list of all nodes which reference each cookbook.
|
27
41
|
|
42
|
+
The '-i' or '--install-cookbook' option will cause knife-audit to copy the knife_audit helper cookbook into the currently configured Chef cookbook_path. If there is already a directory or file there with that name, it will abort. Note that you will need to 'knife cookbook upload knife_audit' once you have done this in order to push the cookbook to your Chef server; in addition, you will need to add the knife_audit cookbook to your node runlists. See 'Helper Cookbook' below for more information.
|
43
|
+
|
28
44
|
**NOTE** knife-audit retrieves an array of *all* nodes present on your chef server for each run. As a result, it is relatively slow; if you have many ( >= 16) nodes, it will take noticeable wallclock time to complete its run. In addition. it may use lots of memory to hold those node objects.
|
29
45
|
|
30
46
|
|
47
|
+
Helper Cookbook
|
48
|
+
---------------
|
49
|
+
|
50
|
+
The helper cookbook (knife_audit) consists of a single recipe (default) with a single resource in it - a ruby_block which saves node.run_state.seen_recipes to the attribute node[:knife_audit][:seen_recipes]. This preserves the *complete* runlist information from seen_recipes, which chef-client does not save to the chef server after constructing it in the compile phase. Since the helper cookbook performs this attribute copy in a ruby_block, it will occur during the execute phase, guaranteeing that seen_recipes is complete (unless your runlist contains a cookbook which modifies the node's runlist!) As a result, knife_audit can be called at any point in your runlist without affecting its function (again, unless your runlist modifies itself; in this case, best to call it first).
|
51
|
+
|
52
|
+
|
31
53
|
Disclaimer
|
32
54
|
----------
|
33
55
|
|
@@ -37,7 +59,9 @@ This is my first knife plugin, and I haven't been using Ruby that long. Plus, I
|
|
37
59
|
License terms
|
38
60
|
-------------
|
39
61
|
Authors:: J.B. Zimmerman
|
62
|
+
|
40
63
|
Copyright:: Copyright (c) 2009-2011 J.B. Zimmerman
|
64
|
+
|
41
65
|
License:: Apache License, Version 2.0
|
42
66
|
|
43
67
|
|
data/knife-audit.gemspec
CHANGED
@@ -5,8 +5,8 @@ require "knife-audit/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "knife-audit"
|
7
7
|
s.version = Knife::Audit::VERSION
|
8
|
-
s.authors = ["
|
9
|
-
s.email = ["
|
8
|
+
s.authors = ["J.B. Zimmerman"]
|
9
|
+
s.email = ["jbzimmerman91@gmail.com"]
|
10
10
|
s.homepage = "https://github.com/jbz/knife-audit"
|
11
11
|
s.summary = %q{A Chef plugin for determining which cookbooks are in use on which nodes of your Chef server or Opscode organization.}
|
12
12
|
s.description = %q{Allows you to safely maintain a chef cookbook set by determining which cookbooks are currently in use by nodes (included in node runlists).}
|
data/lib/chef/knife/audit.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
## Author:: Jacob Zimmermann (<
|
2
|
+
## Author:: Jacob Zimmermann (<jbzimmerman91@gmail.com>)
|
3
3
|
##
|
4
4
|
## Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
## you may not use this file except in compliance with the License.
|
@@ -36,6 +36,21 @@ module KnifeAudit
|
|
36
36
|
:long => "--show-nodelist",
|
37
37
|
:description => "Show all nodes running each cookbook"
|
38
38
|
|
39
|
+
option :all_cookbooks,
|
40
|
+
:short => "-a",
|
41
|
+
:long => "--all-cookbooks",
|
42
|
+
:description => "Show all cookbook references, including those from seen_recipes if available on nodes from helper cookbook"
|
43
|
+
|
44
|
+
option :totals,
|
45
|
+
:short => "-t",
|
46
|
+
:long => "--totals",
|
47
|
+
:description => "Show cookbook count totals for all node types"
|
48
|
+
|
49
|
+
option :install_cookbook,
|
50
|
+
:short => "-i",
|
51
|
+
:long => "--install-cookbook",
|
52
|
+
:description => "Install knife_audit helper cookbook into current chef cookbook directory"
|
53
|
+
|
39
54
|
def run
|
40
55
|
|
41
56
|
if @name_args.empty?
|
@@ -45,6 +60,27 @@ module KnifeAudit
|
|
45
60
|
end
|
46
61
|
|
47
62
|
self.config = Chef::Config.merge!(config)
|
63
|
+
|
64
|
+
# If :install_cookbook flag is set, just install the cookbook and return (exit).
|
65
|
+
if config[:install_cookbook]
|
66
|
+
|
67
|
+
unless config[:cookbook_path]
|
68
|
+
ui.msg("No cookbook path set in Chef config, cannot install cookbook.")
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
source_path = File.dirname(__FILE__) + "/knife_audit_cookbook"
|
73
|
+
dest_path = config[:cookbook_path].first + "/knife_audit"
|
74
|
+
|
75
|
+
if File.exist?(dest_path)
|
76
|
+
ui.msg("knife_audit cookbook already present in cookbook directory #{config[:cookbook_path].first} - aborting...")
|
77
|
+
else
|
78
|
+
FileUtils.copy_entry(source_path, dest_path)
|
79
|
+
ui.msg("knife-audit cookbook copied to Chef cookbook directory #{config[:cookbook_path].first}")
|
80
|
+
end
|
81
|
+
|
82
|
+
return
|
83
|
+
end
|
48
84
|
|
49
85
|
# 1) Get a list (hash, actually, with key of 'name') of cookbooks available on the current server/org
|
50
86
|
# unless we've been given a cookbook/cookbooks on the command line
|
@@ -71,8 +107,10 @@ module KnifeAudit
|
|
71
107
|
|
72
108
|
# add count => 0 to each cookbook hash
|
73
109
|
cookbook_list.each do |name,book|
|
74
|
-
book["count"] = 0
|
110
|
+
book["count"] = 0
|
111
|
+
book["seen_recipe_count"] = 0
|
75
112
|
book["nodes"] = []
|
113
|
+
book["seen_recipe_nodes"] = []
|
76
114
|
end
|
77
115
|
|
78
116
|
|
@@ -89,38 +127,80 @@ module KnifeAudit
|
|
89
127
|
|
90
128
|
# 3a) Get node's runlist
|
91
129
|
|
92
|
-
#
|
93
|
-
|
130
|
+
# Check to see if we need the seen_recipes total or not; if no, skip.
|
131
|
+
# If yes use seen_recipes if it's available. If it's not available, fall back
|
132
|
+
# to the node.recipes contents.
|
133
|
+
if (config[:all_cookbooks] || config[:totals])
|
134
|
+
recipes = (node["knife_audit"] && node["knife_audit"]["seen_recipes"].keys) || node.expand!.recipes.to_a
|
135
|
+
if node["knife_audit"] && node["knife_audit"]["seen_recipes"]
|
136
|
+
node_seen_recipe_flag = true
|
137
|
+
end
|
138
|
+
else
|
139
|
+
# If not, use node.recipes. Using expand!.recipes catches multi-level roles
|
140
|
+
# (roles with roles with recipes, etc.)
|
141
|
+
recipes = node.expand!.recipes.to_a
|
142
|
+
end
|
143
|
+
|
94
144
|
node_cookbook_list = recipes.map{ |x| x.match(/[^\:]+/)[0] }.uniq
|
95
145
|
|
96
146
|
# 3b) For each cookbook in the node runlist, if it's in our cookbook array increment its count and
|
97
147
|
# add the node to its running node array
|
98
148
|
|
99
149
|
node_cookbook_list.each do |cookbook|
|
100
|
-
|
101
|
-
# Up the
|
102
|
-
|
103
|
-
|
104
|
-
|
150
|
+
if cookbook_list.has_key?(cookbook)
|
151
|
+
# Up the appropriate ookbook count and add node to appropriate nodes array
|
152
|
+
if node_seen_recipe_flag
|
153
|
+
cookbook_list[cookbook]["seen_recipe_count"] += 1
|
154
|
+
cookbook_list[cookbook]["seen_recipe_nodes"] << node.name
|
155
|
+
else
|
156
|
+
cookbook_list[cookbook]["count"] += 1
|
157
|
+
cookbook_list[cookbook]["nodes"] << node.name
|
158
|
+
end
|
105
159
|
end
|
106
160
|
end
|
161
|
+
node_seen_recipe_flag = false
|
107
162
|
|
108
163
|
end # step 3 iterate end
|
109
164
|
|
110
165
|
# 4) Output
|
111
166
|
|
112
|
-
|
113
|
-
ui.msg(
|
167
|
+
unless config[:totals]
|
168
|
+
ui.msg("Cookbook audit from node runlists:")
|
169
|
+
|
170
|
+
format_cookbook_runlist_list_for_display(cookbook_list).each do |line|
|
171
|
+
ui.msg(line)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if config[:all_cookbooks]
|
176
|
+
puts("\n")
|
177
|
+
|
178
|
+
ui.msg("Cookbook audit from seen_recipes:")
|
179
|
+
|
180
|
+
format_cookbook_seenlist_list_for_display(cookbook_list).each do |line|
|
181
|
+
ui.msg(line)
|
182
|
+
end
|
114
183
|
end
|
115
184
|
|
185
|
+
if config[:totals]
|
186
|
+
puts("\n")
|
187
|
+
|
188
|
+
ui.msg("Cookbook audit totals - runlist-only nodes + seen_recipes:")
|
189
|
+
|
190
|
+
format_cookbook_totallist_list_for_display(cookbook_list).each do |line|
|
191
|
+
ui.msg(line)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
116
196
|
end # 'run' def end
|
117
197
|
|
118
198
|
|
119
|
-
def
|
199
|
+
def format_cookbook_runlist_list_for_display(item)
|
120
200
|
key_length = item.empty? ? 0 : item.keys.map {|name| name.size }.max + 2
|
121
201
|
if config[:show_nodelist]
|
122
202
|
item.sort.map do |name, cookbook|
|
123
|
-
"#{name.ljust(key_length)} #{cookbook["count"]}
|
203
|
+
"#{name.ljust(key_length)} #{cookbook["count"]} [ #{cookbook["nodes"].join(' ')} ]"
|
124
204
|
end
|
125
205
|
else
|
126
206
|
item.sort.map do |name, cookbook|
|
@@ -128,7 +208,39 @@ module KnifeAudit
|
|
128
208
|
end
|
129
209
|
end
|
130
210
|
|
131
|
-
end #
|
211
|
+
end # format_cokbook_runlist... def end
|
212
|
+
|
213
|
+
def format_cookbook_seenlist_list_for_display(item)
|
214
|
+
key_length = item.empty? ? 0 : item.keys.map {|name| name.size }.max + 2
|
215
|
+
if config[:show_nodelist]
|
216
|
+
item.sort.map do |name, cookbook|
|
217
|
+
"#{name.ljust(key_length)} #{cookbook["seen_recipe_count"]} [ #{cookbook["seen_recipe_nodes"].join(' ')} ]"
|
218
|
+
end
|
219
|
+
else
|
220
|
+
item.sort.map do |name, cookbook|
|
221
|
+
"#{name.ljust(key_length)} #{cookbook["seen_recipe_count"]}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end # format_cokbook_seenlist... def end
|
226
|
+
|
227
|
+
def format_cookbook_totallist_list_for_display(item)
|
228
|
+
key_length = item.empty? ? 0 : item.keys.map {|name| name.size }.max + 2
|
229
|
+
if config[:show_nodelist]
|
230
|
+
item.sort.map do |name, cookbook|
|
231
|
+
cookbook_display = (cookbook["seen_recipe_nodes"] + cookbook["nodes"]).uniq
|
232
|
+
cookbook_count = cookbook["seen_recipe_count"] + cookbook["count"]
|
233
|
+
"#{name.ljust(key_length)} #{cookbook_count} [ #{cookbook_display.join(' ')} ]"
|
234
|
+
end
|
235
|
+
else
|
236
|
+
item.sort.map do |name, cookbook|
|
237
|
+
cookbook_count = cookbook["seen_recipe_count"] + cookbook["count"]
|
238
|
+
"#{name.ljust(key_length)} #{cookbook_count}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end # format_cokbook_seenlist... def end
|
243
|
+
|
132
244
|
|
133
245
|
|
134
246
|
end #class end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
maintainer "J.B. Zimmerman"
|
2
|
+
maintainer_email "jbzimmerman91@gmail.com"
|
3
|
+
license "Apache 2.0"
|
4
|
+
description "A cookbook for saving node auditing data for the knife-audit plugin"
|
5
|
+
long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
|
6
|
+
version "0.0.2"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# Cookbook Name:: knife_audit
|
3
|
+
# Recipe:: default
|
4
|
+
#
|
5
|
+
# Copyright 2011, Medidata
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
|
21
|
+
# Get the :seen_recipes list and save it to a permanent attribute. This is in a ruby_block so that it runs
|
22
|
+
# during execute phase, that way :seen_recipes is complete since it is populated during compile phase. Once
|
23
|
+
# this has been placed in an attribute, the end-of-run node.save will save it so that knife audit can get
|
24
|
+
# the node's full runlist from the chef server.
|
25
|
+
|
26
|
+
ruby_block "get_seen_recipes" do
|
27
|
+
block do
|
28
|
+
runlist = node.run_state[:seen_recipes]
|
29
|
+
node.set["knife_audit"]["seen_recipes"] = runlist
|
30
|
+
end
|
31
|
+
action :create
|
32
|
+
end
|
data/lib/knife-audit/version.rb
CHANGED
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-audit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 2
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.1
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
|
-
-
|
13
|
+
- J.B. Zimmerman
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-10-07 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
22
22
|
description: Allows you to safely maintain a chef cookbook set by determining which cookbooks are currently in use by nodes (included in node runlists).
|
23
23
|
email:
|
24
|
-
-
|
24
|
+
- jbzimmerman91@gmail.com
|
25
25
|
executables: []
|
26
26
|
|
27
27
|
extensions: []
|
@@ -34,6 +34,9 @@ files:
|
|
34
34
|
- Rakefile
|
35
35
|
- knife-audit.gemspec
|
36
36
|
- lib/chef/knife/audit.rb
|
37
|
+
- lib/chef/knife/knife_audit_cookbook/README.rdoc
|
38
|
+
- lib/chef/knife/knife_audit_cookbook/metadata.rb
|
39
|
+
- lib/chef/knife/knife_audit_cookbook/recipes/default.rb
|
37
40
|
- lib/knife-audit/version.rb
|
38
41
|
has_rdoc: true
|
39
42
|
homepage: https://github.com/jbz/knife-audit
|