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 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 (included in node runlists).
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], 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.
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 = ["Jacob Zimmerman"]
9
- s.email = ["jzimmerman@mdsol.com"]
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).}
@@ -1,5 +1,5 @@
1
1
  #
2
- ## Author:: Jacob Zimmermann (<jzimmerman@mdsol.com>)
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
- # using expand!.recipes catches multi-level roles (roles with roles with recipes, etc.)
93
- recipes = node.expand!.recipes.to_a
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
- if cookbook_list.has_key?(cookbook)
101
- # Up the cookbook count
102
- cookbook_list[cookbook]["count"] += 1
103
- # Add the node to the cookbook's nodes array
104
- cookbook_list[cookbook]["nodes"] << node.name
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
- format_cookbook_audit_list_for_display(cookbook_list).each do |line|
113
- ui.msg(line)
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 format_cookbook_audit_list_for_display(item)
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"]} [ #{cookbook["nodes"].join(' ')} ]"
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 # format_cokbook_audit... def 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,8 @@
1
+ = DESCRIPTION:
2
+
3
+ = REQUIREMENTS:
4
+
5
+ = ATTRIBUTES:
6
+
7
+ = USAGE:
8
+
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Knife
2
2
  module Audit
3
- VERSION = "0.0.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
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: 29
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 2
8
9
  - 0
9
- - 1
10
- version: 0.0.1
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
- - Jacob Zimmerman
13
+ - J.B. Zimmerman
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-31 00:00:00 -04:00
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
- - jzimmerman@mdsol.com
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