knife-tidy 1.2.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/lib/chef/knife/tidy_backup_clean.rb +87 -87
- data/lib/chef/knife/tidy_base.rb +11 -11
- data/lib/chef/knife/tidy_notify.rb +69 -69
- data/lib/chef/knife/tidy_server_clean.rb +23 -23
- data/lib/chef/knife/tidy_server_report.rb +40 -40
- data/lib/chef/tidy_acls.rb +33 -33
- data/lib/chef/tidy_common.rb +20 -20
- data/lib/chef/tidy_server.rb +1 -1
- data/lib/chef/tidy_substitutions.rb +11 -11
- data/lib/knife-tidy/version.rb +2 -2
- metadata +5 -101
- data/.gitignore +0 -52
- data/.travis.yml +0 -26
- data/CHANGELOG.md +0 -246
- data/Gemfile +0 -38
- data/README.md +0 -165
- data/Rakefile +0 -58
- data/TODO.md +0 -5
- data/conf/substitutions.json.example +0 -10
- data/knife-tidy.gemspec +0 -32
- data/spec/chef/knife/tidy_backup_clean_spec.rb +0 -0
- data/spec/chef/knife/tidy_base_spec.rb +0 -25
- data/spec/spec_helper.rb +0 -17
@@ -1,58 +1,58 @@
|
|
1
|
-
require
|
1
|
+
require "chef/knife/tidy_base"
|
2
2
|
|
3
3
|
class Chef
|
4
4
|
class Knife
|
5
5
|
class TidyNotify < Knife
|
6
6
|
deps do
|
7
|
-
require
|
8
|
-
require
|
7
|
+
require "ffi_yajl"
|
8
|
+
require "net/smtp"
|
9
9
|
end
|
10
10
|
|
11
|
-
banner
|
11
|
+
banner "knife tidy notify (options)"
|
12
12
|
|
13
13
|
option :smtp_server,
|
14
|
-
short:
|
15
|
-
long:
|
16
|
-
default:
|
17
|
-
description:
|
14
|
+
short: "-s SERVER_NAME",
|
15
|
+
long: "--smtp_server SERVER_NAME",
|
16
|
+
default: "localhost",
|
17
|
+
description: "SMTP Server to be used for emailling reports to organization admins (defaults to localhost)"
|
18
18
|
|
19
19
|
option :smtp_port,
|
20
|
-
short:
|
21
|
-
long:
|
20
|
+
short: "-p SMTP_PORT",
|
21
|
+
long: "--smtp_port SMTP_PORT",
|
22
22
|
default: 25,
|
23
|
-
description:
|
23
|
+
description: "SMTP port to be used for emailling reports to organization admins (defaults to 25)"
|
24
24
|
|
25
25
|
option :smtp_helo,
|
26
|
-
short:
|
27
|
-
long:
|
28
|
-
default:
|
29
|
-
description:
|
26
|
+
short: "-h SMTP_HELO",
|
27
|
+
long: "--smtp_helo SMTP_HELO",
|
28
|
+
default: "localhost",
|
29
|
+
description: "SMTP HELO to be used for emailling reports to organization admins (defaults to localhost)"
|
30
30
|
|
31
31
|
option :smtp_username,
|
32
|
-
short:
|
33
|
-
long:
|
34
|
-
description:
|
32
|
+
short: "-u SMTP_USERNAME",
|
33
|
+
long: "--smtp_username SMTP_USERNAME",
|
34
|
+
description: "SMTP Username to be used for emailling reports to organization admins"
|
35
35
|
|
36
36
|
option :smtp_password,
|
37
|
-
long:
|
38
|
-
description:
|
37
|
+
long: "--smtp_password SMTP_PASSWORD",
|
38
|
+
description: "SMTP Password to be used for emailling reports to organization admins"
|
39
39
|
|
40
40
|
option :smtp_from,
|
41
|
-
long:
|
42
|
-
description:
|
41
|
+
long: "--smtp_from SMTP_FROM",
|
42
|
+
description: "SMTP From address to be used for emailling reports to organization admins"
|
43
43
|
|
44
44
|
option :smtp_use_tls,
|
45
|
-
long:
|
46
|
-
short:
|
45
|
+
long: "--smtp_use_tls",
|
46
|
+
short: "-t",
|
47
47
|
default: false,
|
48
48
|
boolean: true | false,
|
49
|
-
description:
|
49
|
+
description: "Whether TLS should be used for emailling reports to organization admins (defaults to false if omitted)"
|
50
50
|
|
51
51
|
include Knife::TidyBase
|
52
52
|
|
53
53
|
def run
|
54
54
|
reports_dir = tidy.reports_dir
|
55
|
-
report_file_suffixes = [
|
55
|
+
report_file_suffixes = ["_unused_cookbooks.json", "_cookbook_count.json", "_stale_nodes.json"]
|
56
56
|
# Only grab the files matching the report_file_suffixes
|
57
57
|
report_files = Dir["#{reports_dir}/*{#{report_file_suffixes.join(',')}}"]
|
58
58
|
|
@@ -62,11 +62,11 @@ class Chef
|
|
62
62
|
begin
|
63
63
|
org_names = report_files.map { |r_file| r_file.match("#{reports_dir}\/(.*)(#{report_file_suffixes.join('|')})").captures.first }.uniq
|
64
64
|
rescue NoMethodError
|
65
|
-
ui.stderr.puts
|
65
|
+
ui.stderr.puts "Failed to parse json reports files. Please ensure your reports are valid."
|
66
66
|
return
|
67
67
|
end
|
68
68
|
if config[:org_list]
|
69
|
-
filter_orgs = config[:org_list].split(
|
69
|
+
filter_orgs = config[:org_list].split(",")
|
70
70
|
# Take the intersection of org_names and filter_orgs
|
71
71
|
org_names &= filter_orgs
|
72
72
|
end
|
@@ -76,7 +76,7 @@ class Chef
|
|
76
76
|
# Iterate through list of collected organizations and parse any report files into JSON objects
|
77
77
|
|
78
78
|
unless org_names
|
79
|
-
ui.std.puts
|
79
|
+
ui.std.puts "No valid org reports found to send notifications. Exiting."
|
80
80
|
return
|
81
81
|
end
|
82
82
|
|
@@ -99,13 +99,13 @@ class Chef
|
|
99
99
|
ui.info("Fetching admins users for organization #{org}")
|
100
100
|
begin
|
101
101
|
admins = org_admins(org)
|
102
|
-
reports[org][
|
102
|
+
reports[org]["admins"] = admins.map { |name, _data| org_user(org, name) unless name == "pivotal" }
|
103
103
|
rescue Net::HTTPServerException
|
104
104
|
ui.info(" Cannot fetch admin users for organization #{org} as it does not exist on the server")
|
105
105
|
end
|
106
106
|
|
107
107
|
# Build list of email recipients from the collected admin users (display name and email address of each)
|
108
|
-
email_recipients = reports[org][
|
108
|
+
email_recipients = reports[org]["admins"].map { |admin| { name: admin["display_name"], email: admin["email"] } unless admin.nil? }.compact
|
109
109
|
|
110
110
|
# Send a report email to all admin users of the organization
|
111
111
|
ui.info "Sending email reports for organization #{org}"
|
@@ -118,73 +118,73 @@ class Chef
|
|
118
118
|
|
119
119
|
def generate_email(report_data, organization, recipients, report_file_suffixes)
|
120
120
|
mime_boundary = "==Multipart_Boundary_x#{srand}x"
|
121
|
-
message =
|
122
|
-
From: Knife Tidy <#{config[:smtp_from]}>
|
123
|
-
To: #{recipients.map { |recipient| "#{recipient[:name]} <#{recipient[:email]}>" }.join(', ')}
|
124
|
-
MIME-Version: 1.0
|
125
|
-
Subject: Knife Tidy Cleanup Report for Organization "#{organization}"
|
126
|
-
Content-Type: multipart/mixed; boundary="#{mime_boundary}";
|
127
|
-
--#{mime_boundary}
|
128
|
-
Content-type: text/html
|
129
|
-
Content-Transfer-Encoding: 7bit
|
130
|
-
|
131
|
-
The following reports were generated by <a href="https://github.com/chef-customers/knife-tidy">knife-tidy</a>, and contain a list of unused cookbooks and stale nodes for the Chef server organization "#{organization}"
|
132
|
-
#{generate_total_cookbooks_table(report_data, organization)}
|
133
|
-
#{generate_unused_cookbooks_table(report_data, organization)}
|
134
|
-
#{generate_node_table(report_data, organization)}
|
135
|
-
MESSAGE_END
|
121
|
+
message = <<~MESSAGE_END
|
122
|
+
From: Knife Tidy <#{config[:smtp_from]}>
|
123
|
+
To: #{recipients.map { |recipient| "#{recipient[:name]} <#{recipient[:email]}>" }.join(', ')}
|
124
|
+
MIME-Version: 1.0
|
125
|
+
Subject: Knife Tidy Cleanup Report for Organization "#{organization}"
|
126
|
+
Content-Type: multipart/mixed; boundary="#{mime_boundary}";
|
127
|
+
--#{mime_boundary}
|
128
|
+
Content-type: text/html
|
129
|
+
Content-Transfer-Encoding: 7bit
|
130
|
+
|
131
|
+
The following reports were generated by <a href="https://github.com/chef-customers/knife-tidy">knife-tidy</a>, and contain a list of unused cookbooks and stale nodes for the Chef server organization "#{organization}"
|
132
|
+
#{generate_total_cookbooks_table(report_data, organization)}
|
133
|
+
#{generate_unused_cookbooks_table(report_data, organization)}
|
134
|
+
#{generate_node_table(report_data, organization)}
|
135
|
+
MESSAGE_END
|
136
136
|
|
137
137
|
report_file_suffixes.each do |suffix|
|
138
|
-
message +=
|
139
|
-
--#{mime_boundary}
|
140
|
-
Content-Transfer-Encoding:7bit
|
141
|
-
Content-Type: plain/text;name="#{organization}#{suffix}";charset="UTF-8"
|
142
|
-
Content-Disposition: attachment;filename="#{organization}#{suffix}"
|
138
|
+
message += <<~MESSAGE_END
|
139
|
+
--#{mime_boundary}
|
140
|
+
Content-Transfer-Encoding:7bit
|
141
|
+
Content-Type: plain/text;name="#{organization}#{suffix}";charset="UTF-8"
|
142
|
+
Content-Disposition: attachment;filename="#{organization}#{suffix}"
|
143
143
|
|
144
|
-
#{report_data[organization][suffix].to_json}
|
144
|
+
#{report_data[organization][suffix].to_json}
|
145
145
|
|
146
|
-
MESSAGE_END
|
146
|
+
MESSAGE_END
|
147
147
|
end
|
148
148
|
|
149
|
-
message +=
|
150
|
-
--#{mime_boundary}--
|
151
|
-
MESSAGE_END
|
149
|
+
message += <<~MESSAGE_END
|
150
|
+
--#{mime_boundary}--
|
151
|
+
MESSAGE_END
|
152
152
|
puts message
|
153
153
|
message
|
154
154
|
end
|
155
155
|
|
156
156
|
def generate_total_cookbooks_table(report_data, organization)
|
157
157
|
table_start = "<h2>Total Versions by Cookbook</h2><p>This table contains the count of versions of each cookbook stored on the Chef Server.<p><table border='1' cellpadding='1' cellspacing='0'>"
|
158
|
-
table_end =
|
159
|
-
header_string =
|
160
|
-
table_body = if report_data[organization][
|
158
|
+
table_end = "</table><br/>"
|
159
|
+
header_string = "<tr><th>Cookbook Name</th><th>Total Version Count</th></tr>"
|
160
|
+
table_body = if report_data[organization]["_cookbook_count.json"].empty?
|
161
161
|
"<tr><td colspan='2'>No cookbook versions</td></tr>"
|
162
162
|
else
|
163
|
-
report_data[organization][
|
163
|
+
report_data[organization]["_cookbook_count.json"].map { |cookbook_name, cookbook_count| "<tr><td>#{cookbook_name}</td><td>#{cookbook_count}</td></tr>" }.join("\n")
|
164
164
|
end
|
165
165
|
table_start + header_string + table_body + table_end
|
166
166
|
end
|
167
167
|
|
168
168
|
def generate_unused_cookbooks_table(report_data, organization)
|
169
169
|
table_start = "<h2>Unused Cookbooks</h2><p>This table contains cookbook names and the count of their versions that are not currently in the runlists of any nodes.<p><table border='1' cellpadding='1' cellspacing='0'>"
|
170
|
-
table_end =
|
171
|
-
header_string =
|
172
|
-
table_body = if report_data[organization][
|
170
|
+
table_end = "</table><br/>"
|
171
|
+
header_string = "<tr><th>Cookbook Name</th><th>Unused Versions</th></tr>"
|
172
|
+
table_body = if report_data[organization]["_unused_cookbooks.json"].empty?
|
173
173
|
"<tr><td colspan='2'>No unused cookbook versions</td></tr>"
|
174
174
|
else
|
175
|
-
report_data[organization][
|
175
|
+
report_data[organization]["_unused_cookbooks.json"].map { |cookbook_name, cookbook_versions| "<tr><td>#{cookbook_name}</td><td>#{cookbook_versions.join('<br>')}</td></tr>" }.join("\n")
|
176
176
|
end
|
177
177
|
table_start + header_string + table_body + table_end
|
178
178
|
end
|
179
179
|
|
180
180
|
def generate_node_table(report_data, organization)
|
181
181
|
table_start = "<h2>Stale Nodes</h2><p>This table contains nodes that have not checked in to the Chef Server in #{report_data[organization]['_stale_nodes.json']['threshold_days']} days.<p><table border='1' cellpadding='1' cellspacing='0'>"
|
182
|
-
table_end =
|
183
|
-
header_string =
|
184
|
-
table_body = if report_data[organization][
|
182
|
+
table_end = "</table>"
|
183
|
+
header_string = "<tr><th>Node Name</th></tr>"
|
184
|
+
table_body = if report_data[organization]["_stale_nodes.json"].empty? || report_data[organization]["_stale_nodes.json"]["count"] == 0
|
185
185
|
"<tr><td colspan='2'>No stale nodes</td></tr>"
|
186
186
|
else
|
187
|
-
report_data[organization][
|
187
|
+
report_data[organization]["_stale_nodes.json"]["list"].map { |node_name| "<tr><td>#{node_name}</td></tr>" }.join("\n")
|
188
188
|
end
|
189
189
|
table_start + header_string + table_body + table_end
|
190
190
|
end
|
@@ -199,7 +199,7 @@ MESSAGE_END
|
|
199
199
|
|
200
200
|
def org_admins(org)
|
201
201
|
admins = {}
|
202
|
-
rest.get("/organizations/#{org}/groups/admins")[
|
202
|
+
rest.get("/organizations/#{org}/groups/admins")["users"].each do |name|
|
203
203
|
admins[name] = {}
|
204
204
|
end
|
205
205
|
admins
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "chef/knife/tidy_base"
|
2
2
|
|
3
3
|
class Chef
|
4
4
|
class Knife
|
@@ -6,32 +6,32 @@ class Chef
|
|
6
6
|
include Knife::TidyBase
|
7
7
|
|
8
8
|
deps do
|
9
|
-
require
|
10
|
-
require
|
9
|
+
require "ffi_yajl"
|
10
|
+
require "chef/util/threaded_job_queue"
|
11
11
|
end
|
12
12
|
|
13
|
-
banner
|
13
|
+
banner "knife tidy server clean (options)"
|
14
14
|
|
15
15
|
option :backup_path,
|
16
|
-
long:
|
17
|
-
description:
|
16
|
+
long: "--backup-path path/to/backup",
|
17
|
+
description: "The path to the knife-ec-backup backup directory"
|
18
18
|
|
19
19
|
option :concurrency,
|
20
|
-
long:
|
20
|
+
long: "--concurrency THREADS",
|
21
21
|
default: 1,
|
22
|
-
description:
|
22
|
+
description: "Maximum number of simultaneous requests to send (default: 1)"
|
23
23
|
|
24
24
|
option :only_cookbooks,
|
25
|
-
long:
|
26
|
-
description:
|
25
|
+
long: "--only-cookbooks",
|
26
|
+
description: "Only delete unused cookbooks from Chef Server."
|
27
27
|
|
28
28
|
option :only_nodes,
|
29
|
-
long:
|
30
|
-
description:
|
29
|
+
long: "--only-nodes",
|
30
|
+
description: "Only delete stale nodes (and associated clients and ACLs) from Chef Server."
|
31
31
|
|
32
32
|
option :dry_run,
|
33
|
-
long:
|
34
|
-
description:
|
33
|
+
long: "--dry-run",
|
34
|
+
description: "Do not perform any actual deletion, only report on what would have been deleted."
|
35
35
|
|
36
36
|
def run
|
37
37
|
STDOUT.sync = true
|
@@ -41,37 +41,37 @@ class Chef
|
|
41
41
|
configure_chef
|
42
42
|
|
43
43
|
if config[:only_cookbooks] && config[:only_nodes]
|
44
|
-
ui.error
|
44
|
+
ui.error "Cannot use --only-cookbooks AND --only-nodes"
|
45
45
|
exit 1
|
46
46
|
end
|
47
47
|
|
48
48
|
while config[:backup_path].nil?
|
49
49
|
user_value = ui.ask_question("It is not recommended to run this command without specifying a current backup directory.\nPlease specify a backup directory:")
|
50
|
-
config[:backup_path] = user_value ==
|
50
|
+
config[:backup_path] = user_value == "" ? nil : user_value
|
51
51
|
end
|
52
52
|
|
53
53
|
unless ::File.directory?(config[:backup_path])
|
54
|
-
ui.error
|
54
|
+
ui.error "Must specify valid --backup-path"
|
55
55
|
exit 1
|
56
56
|
end
|
57
57
|
|
58
58
|
deletions = if config[:only_cookbooks]
|
59
|
-
|
59
|
+
"cookbooks"
|
60
60
|
elsif config[:only_nodes]
|
61
|
-
|
61
|
+
"nodes (and associated clients and ACLs)"
|
62
62
|
else
|
63
|
-
|
63
|
+
"cookbooks and nodes (and associated clients and ACLs)"
|
64
64
|
end
|
65
65
|
|
66
66
|
orgs = if config[:org_list]
|
67
|
-
config[:org_list].split(
|
67
|
+
config[:org_list].split(",")
|
68
68
|
else
|
69
69
|
all_orgs
|
70
70
|
end
|
71
71
|
|
72
72
|
ui.warn "This operation will affect the following Orgs on #{server.root_url}: #{orgs}"
|
73
73
|
if ::File.exist?(server_warnings_file_path)
|
74
|
-
::File.read(::File.expand_path(
|
74
|
+
::File.read(::File.expand_path("reports/knife-tidy-server-warnings.txt")).each_line do |line|
|
75
75
|
ui.warn(line)
|
76
76
|
end
|
77
77
|
end
|
@@ -147,7 +147,7 @@ class Chef
|
|
147
147
|
end
|
148
148
|
|
149
149
|
def report_files
|
150
|
-
Dir[::File.join(tidy.reports_dir,
|
150
|
+
Dir[::File.join(tidy.reports_dir, "**.json")]
|
151
151
|
end
|
152
152
|
|
153
153
|
def all_orgs
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "chef/knife/tidy_base"
|
2
2
|
|
3
3
|
class Chef
|
4
4
|
class Knife
|
@@ -7,15 +7,15 @@ class Chef
|
|
7
7
|
include Knife::TidyBase
|
8
8
|
|
9
9
|
deps do
|
10
|
-
require
|
10
|
+
require "ffi_yajl"
|
11
11
|
end
|
12
12
|
|
13
13
|
banner "knife tidy server report (options)"
|
14
14
|
|
15
15
|
option :node_threshold,
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
16
|
+
long: "--node-threshold NUM_DAYS",
|
17
|
+
default: 30,
|
18
|
+
description: "Maximum number of days since last checkin before node is considered stale (default: 30)"
|
19
19
|
|
20
20
|
def run
|
21
21
|
ensure_reports_dir!
|
@@ -25,7 +25,7 @@ class Chef
|
|
25
25
|
delete_existing_reports
|
26
26
|
|
27
27
|
orgs = if config[:org_list]
|
28
|
-
config[:org_list].split(
|
28
|
+
config[:org_list].split(",")
|
29
29
|
else
|
30
30
|
all_orgs
|
31
31
|
end
|
@@ -51,23 +51,23 @@ class Chef
|
|
51
51
|
|
52
52
|
nodes.each do |node|
|
53
53
|
# If the node hasn't checked in.
|
54
|
-
if !node[
|
54
|
+
if !node["chef_packages"]
|
55
55
|
# If the node is under an hour old.
|
56
|
-
if (Time.now.to_i - node[
|
57
|
-
unconverged_recent_nodes << node[
|
56
|
+
if (Time.now.to_i - node["ohai_time"].to_i) < 3600
|
57
|
+
unconverged_recent_nodes << node["name"]
|
58
58
|
end
|
59
59
|
next
|
60
60
|
end
|
61
|
-
chef_version = Gem::Version.new(node[
|
61
|
+
chef_version = Gem::Version.new(node["chef_packages"]["chef"]["version"])
|
62
62
|
# If the node has checked in within the node_threshold with a client older than 12.3
|
63
|
-
if chef_version < Gem::Version.new("12.3") && (Time.now.to_i - node[
|
64
|
-
pre_12_3_nodes << node[
|
63
|
+
if chef_version < Gem::Version.new("12.3") && (Time.now.to_i - node["ohai_time"].to_i) <= node_threshold * 86400
|
64
|
+
pre_12_3_nodes << node["name"]
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
nodes.select{|node| !node[
|
69
|
-
node[
|
70
|
-
version = Gem::Version.new(version_hash[
|
68
|
+
nodes.select { |node| !node["cookbooks"].nil? }.each do |node|
|
69
|
+
node["cookbooks"].each do |name, version_hash|
|
70
|
+
version = Gem::Version.new(version_hash["version"]).to_s
|
71
71
|
if used_cookbooks[name]
|
72
72
|
used_cookbooks[name].push(version) unless used_cookbooks[name].include?(version)
|
73
73
|
else
|
@@ -81,17 +81,17 @@ class Chef
|
|
81
81
|
|
82
82
|
stale_nodes = []
|
83
83
|
nodes.each do |n|
|
84
|
-
if (Time.now.to_i - n[
|
85
|
-
stale_nodes.push(n[
|
84
|
+
if (Time.now.to_i - n["ohai_time"].to_i) >= node_threshold * 86400
|
85
|
+
stale_nodes.push(n["name"])
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
stale_nodes_hash = {'threshold_days': node_threshold, 'org_total_node_count': nodes.count, 'count': stale_nodes.count, 'list': stale_nodes}
|
89
|
+
stale_nodes_hash = { 'threshold_days': node_threshold, 'org_total_node_count': nodes.count, 'count': stale_nodes.count, 'list': stale_nodes }
|
90
90
|
stale_orgs.push(org) if stale_nodes.count == nodes.count
|
91
91
|
|
92
|
-
tidy.write_new_file(unused_cookbooks(used_cookbooks, cb_list), ::File.join(tidy.reports_dir, "#{org}_unused_cookbooks.json"), backup=false)
|
93
|
-
tidy.write_new_file(version_count, ::File.join(tidy.reports_dir, "#{org}_cookbook_count.json"), backup=false)
|
94
|
-
tidy.write_new_file(stale_nodes_hash, ::File.join(tidy.reports_dir, "#{org}_stale_nodes.json"), backup=false)
|
92
|
+
tidy.write_new_file(unused_cookbooks(used_cookbooks, cb_list), ::File.join(tidy.reports_dir, "#{org}_unused_cookbooks.json"), backup = false)
|
93
|
+
tidy.write_new_file(version_count, ::File.join(tidy.reports_dir, "#{org}_cookbook_count.json"), backup = false)
|
94
|
+
tidy.write_new_file(stale_nodes_hash, ::File.join(tidy.reports_dir, "#{org}_stale_nodes.json"), backup = false)
|
95
95
|
|
96
96
|
if pre_12_3_nodes.length > 0
|
97
97
|
pre_12_3_message = "#{pre_12_3_nodes.length} nodes in organization #{org} have converged in the last #{node_threshold} days with a chef-client < 12.3. These nodes' cookbook versions WILL NOT be factored in the stale cookbooks versions report. Continuing with the server cleanup will delete cookbooks in-use by these nodes."
|
@@ -113,24 +113,24 @@ class Chef
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def delete_existing_reports
|
116
|
-
files = Dir[::File.join(tidy.reports_dir,
|
116
|
+
files = Dir[::File.join(tidy.reports_dir, "*.json")]
|
117
117
|
unless files.empty?
|
118
118
|
ui.confirm("You have existing reports in #{tidy.reports_dir}. Remove")
|
119
|
-
FileUtils.rm(files, :
|
119
|
+
FileUtils.rm(files, force: true)
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
|
-
# Need the block here to get the search method to invoke multiple searches and
|
123
|
+
# Need the block here to get the search method to invoke multiple searches and
|
124
124
|
# aggregate results for result sets over 1k.
|
125
125
|
def nodes_list(org)
|
126
126
|
node_results = []
|
127
127
|
Chef::Search::Query.new("#{server.root_url}/organizations/#{org}").search(
|
128
|
-
:node,
|
129
|
-
:
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
128
|
+
:node, "*:*",
|
129
|
+
filter_result: {
|
130
|
+
"name" => ["name"],
|
131
|
+
"cookbooks" => ["cookbooks"],
|
132
|
+
"ohai_time" => ["ohai_time"],
|
133
|
+
"chef_packages" => ["chef_packages"],
|
134
134
|
}
|
135
135
|
) do |node|
|
136
136
|
node_results << node
|
@@ -141,8 +141,8 @@ class Chef
|
|
141
141
|
def cookbook_list(org)
|
142
142
|
cb_list = {}
|
143
143
|
rest.get("/organizations/#{org}/cookbooks?num_versions=all").each do |name, data|
|
144
|
-
data[
|
145
|
-
version = Gem::Version.new(version_hash[
|
144
|
+
data["versions"].each do |version_hash|
|
145
|
+
version = Gem::Version.new(version_hash["version"]).to_s
|
146
146
|
if cb_list[name] && !cb_list[name].include?(version)
|
147
147
|
cb_list[name].push(version)
|
148
148
|
else
|
@@ -164,11 +164,11 @@ class Chef
|
|
164
164
|
def unused_cookbooks(used_list, cb_list)
|
165
165
|
unused_list = {}
|
166
166
|
cb_list.each do |name, versions|
|
167
|
-
versions.sort! {|
|
167
|
+
versions.sort! { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) }
|
168
168
|
if used_list[name].nil? # Not in the used list at all (Remove all versions)
|
169
169
|
unused_list[name] = versions
|
170
|
-
elsif used_list[name].sort != versions
|
171
|
-
unused = versions - used_list[name] - [versions.last]
|
170
|
+
elsif used_list[name].sort != versions # Is in the used cookbook list, but version arrays do not match (Find unused versions)
|
171
|
+
unused = versions - used_list[name] - [versions.last] # Don't delete the most recent version as it might not be in a run_list yet.
|
172
172
|
unused_list[name] = unused unless unused.empty?
|
173
173
|
end
|
174
174
|
end
|
@@ -176,7 +176,7 @@ class Chef
|
|
176
176
|
end
|
177
177
|
|
178
178
|
def all_orgs
|
179
|
-
rest.get(
|
179
|
+
rest.get("organizations").keys
|
180
180
|
end
|
181
181
|
|
182
182
|
def all_environments(org)
|
@@ -187,7 +187,7 @@ class Chef
|
|
187
187
|
constraints = {}
|
188
188
|
all_environments(org).each do |env|
|
189
189
|
e = rest.get(env)
|
190
|
-
e[
|
190
|
+
e["cookbook_versions"].each do |cb, version|
|
191
191
|
if constraints[cb]
|
192
192
|
constraints[cb].push(version) unless constraints[cb].include?(version)
|
193
193
|
else
|
@@ -202,7 +202,7 @@ class Chef
|
|
202
202
|
if cb_list[cb]
|
203
203
|
cb_list[cb].each do |v|
|
204
204
|
versions_not_satisfied = []
|
205
|
-
if Gem::Dependency.new(
|
205
|
+
if Gem::Dependency.new("", version).match?("", v)
|
206
206
|
return [v]
|
207
207
|
else
|
208
208
|
versions_not_satisfied.push(v)
|
@@ -214,7 +214,7 @@ class Chef
|
|
214
214
|
else
|
215
215
|
ui.warn("Cookbook #{cb} #{version} is pinned in an environment, but does not exist on the server in this org.")
|
216
216
|
end
|
217
|
-
|
217
|
+
nil
|
218
218
|
end
|
219
219
|
|
220
220
|
def check_environment_pins(used_cookbooks, pins, cb_list)
|
@@ -224,7 +224,7 @@ class Chef
|
|
224
224
|
if used_cookbooks[cb]
|
225
225
|
# This pinned cookbook is in the used list, now check for a matching version.
|
226
226
|
used_cookbooks[cb].each do |v|
|
227
|
-
if Gem::Dependency.new(
|
227
|
+
if Gem::Dependency.new("", version).match?("", v)
|
228
228
|
break
|
229
229
|
end
|
230
230
|
end
|