knife-tidy 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|