knife-tidy 2.0.1 → 2.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23b1bd5cd9472c351eddcf5722b5883529ab52ff7d2823cd1f71ffd050ea382e
4
- data.tar.gz: 47d38da1aaea0f9ea819a3fdac00ed8736196d9429c0629a26f2d8562a4f6549
3
+ metadata.gz: 562e7dd8fc15d4b707b8b3d70c0bc54a3226f9d54d8bcc72ef4cf5436320fc1a
4
+ data.tar.gz: 61a42597e3340788e0f4040b5c251c5fce14622afadb077048886936e445815f
5
5
  SHA512:
6
- metadata.gz: 564fbad7997899cc26a55b3c265edbd7a5e0be3aa518c86e4fb67b651e30a6e55311e0042ea2b0f7a009b1b1e0984894e54b6c23cd643d076097789c9a2e3c2a
7
- data.tar.gz: 3ef4230e711258c422cf14b9a53b15bd9797e917afc79bf1dee2763f5ee748b84d4ee02190e20fe70ba27d65a8a448f0a243229090ef5ca4d8c710d1c521f82c
6
+ metadata.gz: 3c475ece3e2a94d08a521f4262820e88e5103d8e1cc3cc7273dbd6c1ce639d122e2c00904a46fe71ddec0eefee87aa567f7de574fd965764cebe70135f65d7d0
7
+ data.tar.gz: 33c2841cfc1d1c51db87a06c7a61ffd26c630096962e0ce70aac4df6d9ac234818e82c0b7ab2894a0a440fd804cb5a15a05fdbe9d691dc84ca0f1b762157d5b7
@@ -194,7 +194,7 @@ class Chef
194
194
  Dir[::File.join(tidy.backup_path, "organizations/*/cookbooks/chef-sugar*/metadata.rb")].each do |file|
195
195
  ui.stdout.puts "INFO: Searching for known chef-sugar problems when uploading."
196
196
  s = Chef::TidySubstitutions.new(nil, tidy)
197
- version = s.cookbook_version_from_path(file)
197
+ version = tidy.cookbook_version_from_path(::File.dirname(file))
198
198
  patterns = [
199
199
  {
200
200
  search: "^require .*/lib/chef/sugar/version",
@@ -222,7 +222,8 @@ class Chef
222
222
  Chef::TidySubstitutions.new(nil, tidy).sub_in_file(
223
223
  ::File.join(cookbook_path, "metadata.rb"),
224
224
  Regexp.new("^depends +['\"]#{name}['\"]"),
225
- "# depends '#{name}' # knife-tidy was here")
225
+ "# depends '#{name}' # knife-tidy was here"
226
+ )
226
227
  end
227
228
  end
228
229
 
@@ -279,9 +280,7 @@ class Chef
279
280
 
280
281
  def create_minimal_metadata(cookbook_path)
281
282
  name = tidy.cookbook_name_from_path(cookbook_path)
282
- components = cookbook_path.split(File::SEPARATOR)
283
- name_version = components[components.index("cookbooks") + 1]
284
- version = name_version.match(/\d+\.\d+\.\d+/).to_s
283
+ version = tidy.cookbook_version_from_path(cookbook_path)
285
284
  metadata = {}
286
285
  metadata["name"] = name
287
286
  metadata["version"] = version
@@ -398,7 +397,7 @@ class Chef
398
397
  existing_group_data = FFI_Yajl::Parser.parse(::File.read(clients_group_path), symbolize_names: false)
399
398
  existing_group_data["clients"] = [] unless existing_group_data.key?("clients")
400
399
  if existing_group_data["clients"].length != tidy.client_names(org).length
401
- ui.stdout.puts "REPAIRING: Adding #{(existing_group_data['clients'].length - tidy.client_names(org).length).abs} missing clients into #{org}'s client group file #{clients_group_path}"
400
+ ui.stdout.puts "REPAIRING: Adding #{(existing_group_data["clients"].length - tidy.client_names(org).length).abs} missing clients into #{org}'s client group file #{clients_group_path}"
402
401
  existing_group_data["clients"] = (existing_group_data["clients"] + tidy.client_names(org)).uniq
403
402
  ::File.open(clients_group_path, "w") do |f|
404
403
  f.write(Chef::JSONCompat.to_json_pretty(existing_group_data))
@@ -11,42 +11,42 @@ class Chef
11
11
  banner "knife tidy notify (options)"
12
12
 
13
13
  option :smtp_server,
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)"
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: "-p SMTP_PORT",
21
- long: "--smtp_port SMTP_PORT",
22
- default: 25,
23
- description: "SMTP port to be used for emailling reports to organization admins (defaults to 25)"
20
+ short: "-p SMTP_PORT",
21
+ long: "--smtp_port SMTP_PORT",
22
+ default: 25,
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: "-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)"
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: "-u SMTP_USERNAME",
33
- long: "--smtp_username SMTP_USERNAME",
34
- description: "SMTP Username to be used for emailling reports to organization admins"
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: "--smtp_password SMTP_PASSWORD",
38
- description: "SMTP Password to be used for emailling reports to organization admins"
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: "--smtp_from SMTP_FROM",
42
- description: "SMTP From address to be used for emailling reports to organization admins"
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: "--smtp_use_tls",
46
- short: "-t",
47
- default: false,
48
- boolean: true | false,
49
- description: "Whether TLS should be used for emailling reports to organization admins (defaults to false if omitted)"
45
+ long: "--smtp_use_tls",
46
+ short: "-t",
47
+ default: false,
48
+ boolean: true | false,
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
 
@@ -54,13 +54,13 @@ class Chef
54
54
  reports_dir = tidy.reports_dir
55
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
- report_files = Dir["#{reports_dir}/*{#{report_file_suffixes.join(',')}}"]
57
+ report_files = Dir["#{reports_dir}/*{#{report_file_suffixes.join(",")}}"]
58
58
 
59
59
  ui.info "Reading from #{tidy.reports_dir} directory"
60
60
 
61
61
  # Fetch list of organization names from reports directory
62
62
  begin
63
- org_names = report_files.map { |r_file| r_file.match("#{reports_dir}\/(.*)(#{report_file_suffixes.join('|')})").captures.first }.uniq
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
65
  ui.stderr.puts "Failed to parse json reports files. Please ensure your reports are valid."
66
66
  return
@@ -120,7 +120,7 @@ class Chef
120
120
  mime_boundary = "==Multipart_Boundary_x#{srand}x"
121
121
  message = <<~MESSAGE_END
122
122
  From: Knife Tidy <#{config[:smtp_from]}>
123
- To: #{recipients.map { |recipient| "#{recipient[:name]} <#{recipient[:email]}>" }.join(', ')}
123
+ To: #{recipients.map { |recipient| "#{recipient[:name]} <#{recipient[:email]}>" }.join(", ")}
124
124
  MIME-Version: 1.0
125
125
  Subject: Knife Tidy Cleanup Report for Organization "#{organization}"
126
126
  Content-Type: multipart/mixed; boundary="#{mime_boundary}";
@@ -172,13 +172,13 @@ class Chef
172
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]["_unused_cookbooks.json"].map { |cookbook_name, cookbook_versions| "<tr><td>#{cookbook_name}</td><td>#{cookbook_versions.join('<br>')}</td></tr>" }.join("\n")
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
- 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'>"
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
182
  table_end = "</table>"
183
183
  header_string = "<tr><th>Node Name</th></tr>"
184
184
  table_body = if report_data[organization]["_stale_nodes.json"].empty? || report_data[organization]["_stale_nodes.json"]["count"] == 0
@@ -89,6 +89,7 @@ class Chef
89
89
  queue = Chef::Util::ThreadedJobQueue.new
90
90
  unused_cookbooks_file = ::File.join(tidy.reports_dir, "#{org}_unused_cookbooks.json")
91
91
  return unless ::File.exist?(unused_cookbooks_file)
92
+
92
93
  ui.stdout.puts "INFO: Cleaning cookbooks for Org: #{org}, using #{unused_cookbooks_file}"
93
94
  unused_cookbooks = FFI_Yajl::Parser.parse(::File.read(unused_cookbooks_file), symbolize_names: true)
94
95
  unused_cookbooks.keys.each do |cookbook|
@@ -115,6 +116,7 @@ class Chef
115
116
  queue = Chef::Util::ThreadedJobQueue.new
116
117
  stale_nodes_file = ::File.join(tidy.reports_dir, "#{org}_stale_nodes.json")
117
118
  return unless ::File.exist?(stale_nodes_file)
119
+
118
120
  ui.stdout.puts "INFO: Cleaning stale nodes for Org: #{org}, using #{stale_nodes_file}"
119
121
  stale_nodes = FFI_Yajl::Parser.parse(::File.read(stale_nodes_file), symbolize_names: true)
120
122
  stale_nodes[:list].each do |node|
@@ -43,7 +43,7 @@ class Chef
43
43
  nodes = nodes_list(org)
44
44
  db_nodes = rest.get("/organizations/#{org}/nodes")
45
45
  unless nodes.length == db_nodes.length
46
- ood_message = "Search index is out of date! No cleanup action will be taken for #{org}."
46
+ ood_message = "Search index is out of date (search returned #{nodes.length} nodes while the database indicates there are #{db_nodes.length} nodes! No action will be taken for #{org}. Perhaps a 'chef-server-ctl reindex' is in order?"
47
47
  ui.error(ood_message)
48
48
  action_needed(ood_message, server_warnings_file_path)
49
49
  next
@@ -51,7 +51,7 @@ class Chef
51
51
 
52
52
  nodes.each do |node|
53
53
  # If the node hasn't checked in.
54
- if !node["chef_packages"]
54
+ unless node["chef_packages"]
55
55
  # If the node is under an hour old.
56
56
  if (Time.now.to_i - node["ohai_time"].to_i) < 3600
57
57
  unconverged_recent_nodes << node["name"]
@@ -207,8 +207,9 @@ class Chef
207
207
  else
208
208
  versions_not_satisfied.push(v)
209
209
  end
210
+
210
211
  if v == cb_list[cb].last
211
- ui.warn("Pin of #{cb} #{version} not satisfied by current versions of cookbook: [#{versions_not_satisfied.join(', ')}]")
212
+ ui.warn("Pin of #{cb} #{version} not satisfied by current versions of cookbook: [#{versions_not_satisfied.join(", ")}]")
212
213
  end
213
214
  end
214
215
  else
@@ -221,6 +222,7 @@ class Chef
221
222
  pins.each do |cb, versions|
222
223
  versions.each do |version|
223
224
  next if version == "<= 0.0.0"
225
+
224
226
  if used_cookbooks[cb]
225
227
  # This pinned cookbook is in the used list, now check for a matching version.
226
228
  used_cookbooks[cb].each do |v|
@@ -168,6 +168,7 @@ class Chef
168
168
  actors_groups = acl_actors_groups(acl)
169
169
  actors_groups[:actors].each do |actor|
170
170
  next if actor == "pivotal"
171
+
171
172
  if ambiguous_actor?(actor)
172
173
  fix_ambiguous_actor(actor)
173
174
  elsif missing_from_members?(actor)
@@ -12,61 +12,130 @@ class Chef
12
12
  @backup_path = ::File.expand_path(backup_path)
13
13
  end
14
14
 
15
+ #
16
+ # @return [Chef::Knife::UI]
17
+ #
15
18
  def ui
16
19
  @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
17
20
  end
18
21
 
22
+ # The path to the users directory in the backup
23
+ #
24
+ # @return [String]
25
+ #
19
26
  def users_path
20
27
  @users_path ||= ::File.expand_path(::File.join(@backup_path, "users"))
21
28
  end
22
29
 
30
+ # The path to the members.json file in the backup
31
+ #
32
+ # @param [String] org
33
+ #
34
+ # @return [String]
35
+ #
23
36
  def members_path(org)
24
37
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "members.json"))
25
38
  end
26
39
 
40
+ # The path to the invitations.json file in the backup
41
+ #
42
+ # @param [String] org
43
+ #
44
+ # @return [String]
45
+ #
27
46
  def invitations_path(org)
28
47
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "invitations.json"))
29
48
  end
30
49
 
50
+ # The path to the clients directory in the backup
51
+ #
52
+ # @param [String] org
53
+ #
54
+ # @return [String]
55
+ #
31
56
  def clients_path(org)
32
57
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "clients"))
33
58
  end
34
59
 
60
+ # The paths to each of the client json files in the backup
61
+ #
62
+ # @param [String] org
63
+ #
64
+ # @return [Array]
65
+ #
35
66
  def client_names(org)
36
67
  Dir[::File.join(clients_path(org), "*")].map { |dir| ::File.basename(dir, ".json") }
37
68
  end
38
69
 
70
+ # The path to groups directory in the backup
71
+ #
72
+ # @param [String] org
73
+ #
74
+ # @return [String]
75
+ #
39
76
  def groups_path(org)
40
77
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "groups"))
41
78
  end
42
79
 
80
+ # The path to acls directory in the backup
81
+ #
82
+ # @param [String] org
83
+ #
84
+ # @return [String]
85
+ #
43
86
  def org_acls_path(org)
44
87
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "acls"))
45
88
  end
46
89
 
90
+ # The path to user_acls directory in the backup
91
+ #
92
+ # @return [String]
93
+ #
47
94
  def user_acls_path
48
95
  @user_acls_path ||= ::File.expand_path(::File.join(@backup_path, "user_acls"))
49
96
  end
50
97
 
98
+ # The path to cookbooks directory in the backup
99
+ #
100
+ # @param [String] org
101
+ #
102
+ # @return [String]
103
+ #
51
104
  def cookbooks_path(org)
52
105
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "cookbooks"))
53
106
  end
54
107
 
108
+ # The path to roles directory in the backup
109
+ #
110
+ # @param [String] org
111
+ #
112
+ # @return [String]
113
+ #
55
114
  def roles_path(org)
56
115
  ::File.expand_path(::File.join(@backup_path, "organizations", org, "roles"))
57
116
  end
58
117
 
118
+ # The path to the org directory in the backup
119
+ #
120
+ # @param [String] org
121
+ #
122
+ # @return [String]
123
+ #
59
124
  def org_path(org)
60
125
  ::File.expand_path(::File.join(@backup_path, "organizations", org))
61
126
  end
62
127
 
128
+ # generate a bogus, but valid email
129
+ #
130
+ # @return [String]
131
+ #
63
132
  def unique_email
64
133
  (0...8).map { (65 + rand(26)).chr }.join.downcase +
65
134
  "@" + (0...8).map { (65 + rand(26)).chr }.join.downcase + ".com"
66
135
  end
67
136
 
68
137
  def save_user(user)
69
- ::File.open(::File.join(users_path, "#{user['username']}.json"), "w+") do |f|
138
+ ::File.open(::File.join(users_path, "#{user["username"]}.json"), "w+") do |f|
70
139
  f.write(FFI_Yajl::Encoder.encode(user, pretty: true))
71
140
  end
72
141
  end
@@ -80,10 +149,45 @@ class Chef
80
149
  end
81
150
  end
82
151
 
152
+ #
153
+ # Determine the cookbook name from path
154
+ #
155
+ # @param [String] path The path of the cookbook.
156
+ #
157
+ # @return [String] The cookbook's name
158
+ #
159
+ # @example
160
+ # cookbook_version_from_path('/data/chef_backup/snapshots/20191008040001/organizations/myorg/cookbooks/chef-sugar-5.0.4') => 'chef-sugar'
161
+ #
83
162
  def cookbook_name_from_path(path)
84
163
  ::File.basename(path, "-*")
85
164
  end
86
165
 
166
+ #
167
+ # Determine the cookbook version from a path.
168
+ #
169
+ # @param [String] path The path of the cookbook.
170
+ #
171
+ # @return [String] The version of the cookbook.
172
+ #
173
+ # @example
174
+ # cookbook_version_from_path('/data/chef_backup/snapshots/20191008040001/organizations/myorg/cookbooks/chef-sugar-5.0.4') => '5.0.4'
175
+ # cookbook_version_from_path('/data/chef_backup/snapshots/20191008040001/organizations/myorg/cookbooks/chef-sugar-5.0.4/recipe/default.rb') => '5.0.4'
176
+ # cookbook_version_from_path('/data/chef_backup/snapshots/20191008040001/organizations/myorg/cookbooks/chef-sugar-5.0.4/files/cookbooks/default.rb') => '5.0.4'
177
+ #
178
+ def cookbook_version_from_path(path)
179
+ dirs = path.split(File::SEPARATOR)
180
+
181
+ until dirs.empty?
182
+ version_match = dirs[-1].match(/\d+\.\d+\.\d+/)
183
+ if dirs[-2] == "cookbooks" && version_match # we found the cookbook version not something that looks like one inside a cookbook path
184
+ return version_match.to_s
185
+ else
186
+ dirs.pop
187
+ end
188
+ end
189
+ end
190
+
87
191
  def global_user_names
88
192
  @global_user_names ||= Dir[::File.join(@backup_path, "users", "*")].map { |dir| ::File.basename(dir, ".json") }
89
193
  end
@@ -7,7 +7,7 @@ class Chef
7
7
  end
8
8
 
9
9
  def self.from_chef_server_url(url)
10
- url = url.gsub(/\/organizations\/+[^\/]+\/*$/, "")
10
+ url = url.gsub(%r{/organizations/+[^/]+/*$}, "")
11
11
  Chef::Server.new(url)
12
12
  end
13
13
  end
@@ -28,14 +28,7 @@ class Chef
28
28
  FileUtils.cp(bp, ::File.join(Dir.pwd, "substitutions.json"))
29
29
  end
30
30
 
31
- def cookbook_version_from_path(path)
32
- components = path.split(File::SEPARATOR)
33
- name_version = components[components.index("cookbooks") + 1]
34
- name_version.match(/\d+\.\d+\.\d+/).to_s
35
- end
36
-
37
- def revert
38
- end
31
+ def revert; end
39
32
 
40
33
  def sub_in_file(path, search, replace)
41
34
  temp_file = Tempfile.new("tidy")
@@ -68,7 +61,7 @@ class Chef
68
61
  @data[entry][glob].each do |substitution|
69
62
  search = Regexp.new(substitution["pattern"])
70
63
  replace = substitution["replace"].dup
71
- replace.gsub!(/\!COOKBOOK_VERSION\!/) { |_m| "'" + cookbook_version_from_path(file) + "'" }
64
+ replace.gsub!(/\!COOKBOOK_VERSION\!/) { |_m| "'" + @tidy.cookbook_version_from_path(file) + "'" }
72
65
  sub_in_file(file, search, replace)
73
66
  end
74
67
  end
@@ -1,4 +1,4 @@
1
1
  module KnifeTidy
2
- VERSION = "2.0.1".freeze
2
+ VERSION = "2.0.6".freeze
3
3
  MAJOR, MINOR, TINY = VERSION.split(".")
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-tidy
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Miller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-07 00:00:00.000000000 Z
11
+ date: 2019-12-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Report on stale Chef Server nodes and cookbooks and clean up data integrity
14
14
  issues in a knife-ec-backup object based backup
@@ -48,8 +48,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
48
  - !ruby/object:Gem::Version
49
49
  version: '0'
50
50
  requirements: []
51
- rubyforge_project:
52
- rubygems_version: 2.7.7
51
+ rubygems_version: 3.0.3
53
52
  signing_key:
54
53
  specification_version: 4
55
54
  summary: Report on stale Chef Server nodes and cookbooks and clean up data integrity