knife-tidy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4617020ad74d1acc6de5f6c5e65c7f3506455689
4
+ data.tar.gz: be95ccdc41759a9949aa6ee3aa6afe221486c995
5
+ SHA512:
6
+ metadata.gz: 4170963ff8992beec932a57db8715fa0edba110edee1c643c3f277d4d9b2568edb7ea846cee7033bfa397c4bcd699f5dc0a6aff1494b808c519c461c6269e810
7
+ data.tar.gz: acc9b53c4944f77bc0879805142be99b7143a891a2feb320a80767f2c89879c651d606f9fd56234ddc1801c13b59c17b61a86b3fc12e65140baa36bbc67ec212
data/.gitignore ADDED
@@ -0,0 +1,52 @@
1
+ *.rbc
2
+ .config
3
+ coverage
4
+ InstalledFiles
5
+ lib/bundler/man
6
+ pkg
7
+ rdoc
8
+ spec/reports
9
+ test/tmp
10
+ test/version_tmp
11
+ tmp
12
+ _Store
13
+ reports/
14
+ conf/*.json
15
+ *~
16
+ *#
17
+ .#*
18
+ \#*#
19
+ .*.sw[a-z]
20
+ *.un~
21
+ *.tmp
22
+ *.bk
23
+ *.bkup
24
+
25
+ # ruby/bundler files
26
+ .ruby-version
27
+ .ruby-gemset
28
+ .rvmrc
29
+ Gemfile.lock
30
+ .bundle
31
+ *.gem
32
+
33
+ # YARD artifacts
34
+ .yardoc
35
+ _yardoc
36
+ doc/
37
+ .idea
38
+
39
+ # chef stuff
40
+ Berksfile.lock
41
+ .kitchen
42
+ .kitchen.local.yml
43
+ vendor/
44
+ .coverage/
45
+ .zero-knife.rb
46
+ Policyfile.lock.json
47
+ .chef/
48
+
49
+ # vagrant stuff
50
+ .vagrant/
51
+ .vagrant.d/
52
+ .kitchen/
data/Gemfile ADDED
@@ -0,0 +1,38 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ if vsn = ENV['TRAVIS_CHEF_VERSION']
6
+ if m = /branch:(?<branch>.*)$/.match(vsn)
7
+ gem 'chef', git: 'https://github.com/chef/chef', branch: m[:branch]
8
+ else
9
+ gem 'chef', vsn
10
+ end
11
+ end
12
+
13
+ group :development do
14
+ gem 'rspec'
15
+ gem 'rake'
16
+ gem 'simplecov'
17
+ gem 'fakefs'
18
+ gem "chefstyle", git: "https://github.com/chef/chefstyle.git"
19
+ gem "chef-zero"
20
+ end
21
+
22
+ group :changelog do
23
+ gem "github_changelog_generator", git: "https://github.com/chef/github-changelog-generator"
24
+ end
25
+
26
+ # This is here instead of gemspec so that we can
27
+ # override which Chef gem to use when we do testing
28
+ # Possibilities in the future include using environmental
29
+ # variables, thus allowing us to to have Travis support
30
+
31
+ # Examples you can use in Gemfile.local
32
+ # gem 'chef', '~> 10.28'
33
+ # gem 'chef' # latest
34
+ # gem 'chef', git: 'git://github.com/mal/chef.git', branch: 'CHEF-3307'
35
+
36
+ # If you want to load debugging tools into the bundle exec sandbox,
37
+ # # add these additional dependencies into Gemfile.local
38
+ eval(IO.read(__FILE__ + '.local'), binding) if File.exists?(__FILE__ + '.local')
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # knife tidy
2
+
3
+ # Summary
4
+
5
+ This Chef Knife plugin has two primary purposes:
6
+ * Report on the state of Chef Server objects that can be tidied up (Future: clean up objects)
7
+ * Clean up data integrity issues from a object backup created by [knife-ec-backup](https://github.com/chef/knife-ec-backup)
8
+
9
+ # Requirements
10
+
11
+ A current Chef Client. Can easily be installed via [Chef DK](https://github.com/chef/chef-dk#installation)
12
+
13
+ # Installation
14
+
15
+ Via Gem
16
+ ```bash
17
+ gem install knife-tidy
18
+ ```
19
+
20
+ Via Source
21
+ ```bash
22
+ git clone https://github.com/jeremymv2/knife-tidy.git
23
+ cd knife-tidy
24
+ gem build knife-tidy.gemspec && gem install knife-tidy-*.gem --no-ri --no-rdoc
25
+ ```
26
+
27
+ ## Common Options
28
+
29
+ The following options are supported across all subcommands:
30
+
31
+ * `--orgs ORG1,ORG2`:
32
+ Only apply to objects in the named organizations (default: all orgs)
33
+
34
+ # knife tidy server report (options)
35
+
36
+ ## Options
37
+
38
+ * `--node-threshold NUM_DAYS`
39
+ Maximum number of days since last checkin before node is considered stale (default: 30)
40
+
41
+ ## Notes
42
+ Generates json reports as such:
43
+
44
+ File Name | Contents
45
+ --- | ---
46
+ <org>_<threshold_num>d_stale_nodes.json | Nodes in that org that have not checked in for the number of days specified.
47
+ <org>_cookbook_count.json | Number of cookbook versions for each cookbook that that org.
48
+ <org>_unused_cookbooks.json | List of cookbooks and versions that do not appear to be in-use for that org. This is determined by checking the versioned run list of each of the nodes in the org.
49
+
50
+ # knife tidy backup clean (options)
51
+
52
+ ## Options
53
+
54
+ * `--repo-path /path/to/chef-repo`:
55
+ The Chef Repo to report on or change (such as one created from a
56
+ [knife-ec-backup](https://github.com/chef/knife-ec-backup)
57
+
58
+ ## Notes
59
+
60
+ * Items [Addressed](ITEMS_CLEANED.md)
61
+ * [To Do](TODO_LIST.md)
62
+
63
+ # Credits
64
+
65
+ * Server Report was ported from Nolan Davidson's [chef-cleanup](https://github.com/nsdavidson/chef-cleanup)
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require_relative 'tasks/maintainers'
4
+
5
+ # Style tests. cookstyle (rubocop) and Foodcritic
6
+ namespace :style do
7
+ begin
8
+ require 'cookstyle'
9
+ require 'rubocop/rake_task'
10
+
11
+ desc 'Run Ruby style checks'
12
+ RuboCop::RakeTask.new(:ruby)
13
+ rescue LoadError => e
14
+ puts ">>> Gem load error: #{e}, omitting style:ruby" unless ENV['CI']
15
+ end
16
+
17
+ begin
18
+ require 'foodcritic'
19
+
20
+ desc 'Run Chef style checks'
21
+ FoodCritic::Rake::LintTask.new(:chef) do |t|
22
+ t.options = {
23
+ fail_tags: ['any'],
24
+ progress: true,
25
+ }
26
+ end
27
+ rescue LoadError
28
+ puts ">>> Gem load error: #{e}, omitting style:chef" unless ENV['CI']
29
+ end
30
+ end
31
+
32
+ desc 'Run all style checks'
33
+ task style: ['style:chef', 'style:ruby']
34
+
35
+ # ChefSpec
36
+ begin
37
+ require 'rspec/core/rake_task'
38
+
39
+ desc 'Run ChefSpec examples'
40
+ RSpec::Core::RakeTask.new(:spec)
41
+ rescue LoadError => e
42
+ puts ">>> Gem load error: #{e}, omitting spec" unless ENV['CI']
43
+ end
44
+
45
+ # Integration tests. Kitchen.ci
46
+ namespace :integration do
47
+ begin
48
+ require 'kitchen/rake_tasks'
49
+
50
+ desc 'Run kitchen integration tests'
51
+ Kitchen::RakeTasks.new
52
+ rescue StandardError => e
53
+ puts ">>> Kitchen error: #{e}, omitting #{task.name}" unless ENV['CI']
54
+ end
55
+ end
56
+
57
+ namespace :supermarket do
58
+ begin
59
+ require 'stove/rake_task'
60
+
61
+ desc 'Publish cookbook to Supermarket with Stove'
62
+ Stove::RakeTask.new
63
+ rescue LoadError => e
64
+ puts ">>> Gem load error: #{e}, omitting #{task.name}" unless ENV['CI']
65
+ end
66
+ end
67
+
68
+ # Default
69
+ task default: %w(style spec)
@@ -0,0 +1,14 @@
1
+ {
2
+ "chef-sugar":{
3
+ "organizations/*/cookbooks/chef-sugar*/metadata.rb":[
4
+ {
5
+ "pattern":"require +File.expand_path('../lib/chef/sugar/version', __FILE__)",
6
+ "replace":"# require File.expand_path('../lib/chef/sugar/version', __FILE__)"
7
+ },
8
+ {
9
+ "pattern":"version *Chef::Sugar::VERSION",
10
+ "replace":"# version !COOKBOOK_VERSION!"
11
+ }
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,32 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'knife-tidy/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "knife-tidy"
6
+ s.version = KnifeTidy::VERSION
7
+ s.version = "#{s.version}-pre#{ENV['TRAVIS_BUILD_NUMBER']}" if ENV["TRAVIS"]
8
+ s.has_rdoc = true
9
+ s.authors = ["Jeremy Miller"]
10
+ s.email = ["jmiller@chef.io"]
11
+ s.summary = "Report on stale Chef Server nodes and cookbooks and clean up data integrity issues in a knife-ec-backup object based backup"
12
+ s.description = s.summary
13
+ s.homepage = "https://github.com/jeremymv2/knife-tidy"
14
+ s.license = "Apache License, v2.0"
15
+ s.files = `git ls-files`.split("\n")
16
+ s.require_paths = ["lib"]
17
+
18
+ s.required_ruby_version = ">= 2.2.0"
19
+
20
+ s.add_development_dependency "rake", "~> 11.0"
21
+ s.add_development_dependency "rspec", "~> 3.4"
22
+ s.add_development_dependency "aruba", "~> 0.6"
23
+ s.add_development_dependency "simplecov", "~> 0.9"
24
+ s.add_development_dependency "simplecov-console", "~> 0.2"
25
+ if ENV.key?("TRAVIS_BUILD") && RUBY_VERSION == "2.1.9"
26
+ # Test version of Chef with Chef Zero before
27
+ # /orgs/org/users/user/keys endpoint was added.
28
+ s.add_development_dependency "chef", "12.8.1"
29
+ else # Test most current version of Chef on 2.2.2
30
+ s.add_development_dependency :chef
31
+ end
32
+ end
@@ -0,0 +1,111 @@
1
+ require 'chef/knife/tidy_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class TidyBackupClean < Knife
6
+
7
+ include Knife::TidyBase
8
+
9
+ deps do
10
+ require 'chef/cookbook_loader'
11
+ require 'chef/cookbook/metadata'
12
+ require 'chef/tidy_substitutions'
13
+ end
14
+
15
+ option :backup_path,
16
+ :long => '--backup-path path/to/backup',
17
+ :description => 'The path to the knife-ec-backup backup directory'
18
+
19
+ option :gsub_file,
20
+ :long => '--gsub-file path/to/gsub/file',
21
+ :description => 'The path to the file used for substitutions'
22
+
23
+ def run
24
+ unless config[:backup_path]
25
+ ui.error 'Must specify --backup-path'
26
+ exit 1
27
+ end
28
+
29
+ global_users.each do |user|
30
+ validate_user(user)
31
+ end
32
+
33
+ if config[:gsub_file]
34
+ if config[:gen_gsub_template]
35
+ Chef::TidySubstitutions.new(substitutions_file).boiler_plate
36
+ else
37
+ Chef::TidySubstitutions.new(substitutions_file, backup_path_expanded).run_substitutions
38
+ end
39
+ end
40
+
41
+ orgs.each do |org|
42
+ fix_self_dependencies(org)
43
+ load_cookbooks(org)
44
+ generate_new_metadata(org)
45
+ end
46
+ end
47
+
48
+ def load_cookbooks(org)
49
+ cl = Chef::CookbookLoader.new(cookbooks_path_expanded(org))
50
+ for_each_cookbook_basename(org) do |cookbook|
51
+ ui.info "Loading #{cookbook}"
52
+ ret = cl.load_cookbook(cookbook)
53
+ if ret.nil?
54
+ ui.error "Something's wrong with the #{cookbook} cookbook - cannot load it!"
55
+ end
56
+ end
57
+ rescue LoadError => e
58
+ ui.error e
59
+ ui.error 'Look at the cookbook above and determine what in the metadata.rb is causing the exception and rectify manually'
60
+ exit 1
61
+ end
62
+
63
+ def generate_new_metadata(org)
64
+ for_each_cookbook_path(org) do |cookbook_path|
65
+ generate_metadata_from_file(cookbook_name_from_path(cookbook_path), cookbook_path)
66
+ end
67
+ end
68
+
69
+ def fix_self_dependencies(org)
70
+ for_each_cookbook_path(org) do |cookbook_path|
71
+ name = cookbook_name_from_path(cookbook_path)
72
+ md_path = ::File.join(cookbook_path, 'metadata.rb')
73
+ unless ::File.exist?(md_path)
74
+ ui.warn "No metadata.rb in #{cookbook_path} - skipping"
75
+ next
76
+ end
77
+ Chef::TidySubstitutions.new.sub_in_file(
78
+ ::File.join(cookbook_path, 'metadata.rb'),
79
+ Regexp.new("^depends +['\"]#{name}['\"]"),
80
+ "# depends '#{name}' # knife-tidy was here")
81
+ end
82
+ end
83
+
84
+ def generate_metadata_from_file(cookbook, path)
85
+ md_path = ::File.join(path, 'metadata.rb')
86
+ unless ::File.exist?(md_path)
87
+ ui.warn "No metadata.rb in #{path} - skipping"
88
+ return
89
+ end
90
+ ui.info " Generating new metadata.json for #{path}"
91
+ md = Chef::Cookbook::Metadata.new
92
+ md.name(cookbook)
93
+ md.from_file(md_path)
94
+ json_file = ::File.join(path, 'metadata.json')
95
+ ::File.open(json_file, 'w') do |f|
96
+ f.write(Chef::JSONCompat.to_json_pretty(md))
97
+ end
98
+ rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
99
+ ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
100
+ ui.stderr.puts "in #{file}:"
101
+ ui.stderr.puts
102
+ ui.stderr.puts e.message
103
+ exit 1
104
+ end
105
+
106
+ def validate_user(user)
107
+ ui.info "Validating user #{user}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,97 @@
1
+ # Author:: Jeremy Miller (<jmiller@chef.io>)
2
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'chef/knife'
19
+ require 'chef/server_api'
20
+
21
+ class Chef
22
+ class Knife
23
+ module TidyBase
24
+
25
+ def self.included(includer)
26
+ includer.class_eval do
27
+
28
+ deps do
29
+ require 'chef/tidy_server'
30
+ end
31
+
32
+ option :org_list,
33
+ :long => "--orgs ORG1,ORG2",
34
+ :description => "Only apply to objects in the named organizations"
35
+ end
36
+ end
37
+
38
+ def server
39
+ @server ||= if Chef::Config.chef_server_root.nil?
40
+ ui.warn("chef_server_root not found in knife configuration; using chef_server_url")
41
+ Chef::TidyServer.from_chef_server_url(Chef::Config.chef_server_url)
42
+ else
43
+ Chef::TidyServer.new(Chef::Config.chef_server_root)
44
+ end
45
+ end
46
+
47
+ def rest
48
+ @rest ||= Chef::ServerAPI.new(server.root_url, {:api_version => "0"})
49
+ end
50
+
51
+ def backup_path_expanded
52
+ ::File.expand_path(config[:backup_path])
53
+ end
54
+
55
+ def cookbook_name_from_path(path)
56
+ ::File.basename(path, '-*')
57
+ end
58
+
59
+ def cookbooks_path_expanded(org)
60
+ ::File.expand_path(::File.join(backup_path_expanded, 'organizations', org, 'cookbooks'))
61
+ end
62
+
63
+ def substitutions_file
64
+ ::File.expand_path(config[:gsub_file])
65
+ end
66
+
67
+ def global_users
68
+ Dir[::File.join(backup_path_expanded, 'users', '*')].map { |dir| ::File.basename(dir, '.json') }
69
+ end
70
+
71
+ def orgs
72
+ if config[:org_list]
73
+ config[:org_list].split(',')
74
+ else
75
+ Dir[::File.join(backup_path_expanded, 'organizations', '*')].map { |dir| ::File.basename(dir) }
76
+ end
77
+ end
78
+
79
+ def for_each_cookbook_basename(org)
80
+ cookbooks_seen = []
81
+ Dir[::File.join(cookbooks_path_expanded(org), '**-**')].each do |cookbook|
82
+ name = cookbook_name_from_path(cookbook)
83
+ unless cookbooks_seen.include?(name)
84
+ cookbooks_seen.push(name)
85
+ yield name
86
+ end
87
+ end
88
+ end
89
+
90
+ def for_each_cookbook_path(org)
91
+ Dir[::File.join(cookbooks_path_expanded(org), '**')].each do |cookbook|
92
+ yield cookbook
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,132 @@
1
+ require 'chef/knife/tidy_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class TidyServerReport < Knife
6
+
7
+ include Knife::TidyBase
8
+
9
+ deps do
10
+ require 'ffi_yajl'
11
+ end
12
+
13
+ banner "knife tidy server report (OPTIONS)"
14
+
15
+ option :node_threshold,
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
+
20
+ def run
21
+ ensure_reports_dir!
22
+
23
+ ui.warn "Writing to #{reports_dir} directory"
24
+
25
+ orgs = if config[:org_list]
26
+ config[:org_list].split(',')
27
+ else
28
+ all_orgs
29
+ end
30
+
31
+ stale_orgs = []
32
+ node_threshold = config[:node_threshold]
33
+
34
+ orgs.each do |org|
35
+ ui.info " Organization: #{org}"
36
+ cb_list = cookbook_list(org)
37
+ version_count = cookbook_count(cb_list).sort_by(&:last).reverse.to_h
38
+ used_cookbooks = {}
39
+ nodes = nodes_list(org)[0]
40
+
41
+ nodes.select{|node| !node['cookbooks'].nil?}.each do |node|
42
+ node['cookbooks'].each do |name, version_hash|
43
+ version = Gem::Version.new(version_hash['version']).to_s
44
+ if used_cookbooks[name] && !used_cookbooks[name].include?(version)
45
+ used_cookbooks[name].push(version)
46
+ else
47
+ used_cookbooks[name] = [version]
48
+ end
49
+ end
50
+ end
51
+
52
+ stale_nodes = []
53
+ nodes.each do |n|
54
+ if (Time.now.to_i - n['ohai_time'].to_i) >= node_threshold * 86400
55
+ stale_nodes.push(n['name'])
56
+ end
57
+ end
58
+
59
+ stale_nodes_hash = {'threshold_days': node_threshold, 'count': stale_nodes.count, 'list': stale_nodes}
60
+ stale_orgs.push(org) if stale_nodes.count == nodes.count
61
+
62
+ report("#{org}_unused_cookbooks.json", unused_cookbooks(used_cookbooks, cb_list))
63
+ report("#{org}_unused_cookbooks.json", unused_cookbooks(used_cookbooks, cb_list))
64
+ report("#{org}_cookbook_count.json", version_count)
65
+ report("#{org}_#{node_threshold}d_stale_nodes.json", stale_nodes_hash)
66
+ end
67
+ end
68
+
69
+ def report(file_name, content)
70
+ ::File.write(::File.join(reports_dir, file_name), FFI_Yajl::Encoder.encode(content, pretty: true))
71
+ end
72
+
73
+ def reports_dir
74
+ ::File.join(Dir.pwd, 'reports')
75
+ end
76
+
77
+ def ensure_reports_dir!
78
+ Dir.mkdir(reports_dir) unless Dir.exist?(reports_dir)
79
+ end
80
+
81
+ def nodes_list(org)
82
+ Chef::Search::Query.new("#{server.root_url}/organizations/#{org}").search(
83
+ :node, '*:*',
84
+ :filter_result => {
85
+ 'name' => ['name'],
86
+ 'cookbooks' => ['cookbooks'],
87
+ 'ohai_time' => ['ohai_time']
88
+ }
89
+ )
90
+ end
91
+
92
+ def cookbook_list(org)
93
+ cb_list = {}
94
+ rest.get("/organizations/#{org}/cookbooks?num_versions=all").each do |name, data|
95
+ data['versions'].each do |version_hash|
96
+ version = Gem::Version.new(version_hash['version']).to_s
97
+ if cb_list[name] && !cb_list[name].include?(version)
98
+ cb_list[name].push(version)
99
+ else
100
+ cb_list[name] = [version]
101
+ end
102
+ end
103
+ end
104
+ cb_list
105
+ end
106
+
107
+ def cookbook_count(cb_list)
108
+ cb_count_list = {}
109
+ cb_list.each do |name, versions|
110
+ cb_count_list[name] = versions.count
111
+ end
112
+ cb_count_list
113
+ end
114
+
115
+ def unused_cookbooks(used_list, cb_list)
116
+ unused_list = {}
117
+ cb_list.each do |name, versions|
118
+ if used_list[name].nil? # Not in the used list at all (Remove all versions)
119
+ unused_list[name] = versions
120
+ elsif used_list[name].sort != versions # Is in the used cookbook list, but version arrays do not match (Find unused versions)
121
+ unused_list[name] = versions - used_list[name]
122
+ end
123
+ end
124
+ unused_list
125
+ end
126
+
127
+ def all_orgs
128
+ rest.get('organizations').keys
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,14 @@
1
+ class Chef
2
+ class TidyServer
3
+ attr_accessor :root_url
4
+
5
+ def initialize(root_url)
6
+ @root_url = root_url
7
+ end
8
+
9
+ def self.from_chef_server_url(url)
10
+ url = url.gsub(/\/organizations\/+[^\/]+\/*$/, '')
11
+ Chef::Server.new(url)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,81 @@
1
+ require 'ffi_yajl'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+ require 'chef/log'
5
+
6
+ class Chef
7
+ class TidySubstitutions
8
+ class NoSubstitutionFile < RuntimeError; end
9
+
10
+ attr_accessor :file_path, :backup_path, :data
11
+
12
+ def initialize(file_path = nil, backup_path = nil)
13
+ @file_path = file_path
14
+ @backup_path = backup_path
15
+ end
16
+
17
+ # Load the substitutions from disk
18
+ #
19
+ # @return [Hash]
20
+ #
21
+ def load_data
22
+ Chef::Log.info "Loading substitutions from #{file_path}"
23
+ @data = FFI_Yajl::Parser.parse(::File.read(@file_path), symbolize_names: false)
24
+ rescue Errno::ENOENT
25
+ raise NoSubstitutionFile, file_path
26
+ end
27
+
28
+ def boiler_plate
29
+
30
+ end
31
+
32
+ def cookbook_version_from_path(path)
33
+ components = path.split(File::SEPARATOR)
34
+ name_version = components[components.index('cookbooks')+1]
35
+ name_version.match(/\d+\.\d+\.\d+/).to_s
36
+ end
37
+
38
+ def revert
39
+
40
+ end
41
+
42
+ def sub_in_file(path, search, replace)
43
+ temp_file = Tempfile.new('tidy')
44
+ begin
45
+ File.open(path, 'r') do |file|
46
+ file.each_line do |line|
47
+ if line.match(search)
48
+ temp_file.puts replace
49
+ Chef::Log.info " ++ #{path}"
50
+ else
51
+ temp_file.puts line
52
+ end
53
+ end
54
+ end
55
+ temp_file.close
56
+ FileUtils.mv(path, "#{path}.orig") unless ::File.exist?("#{path}.orig")
57
+ FileUtils.mv(temp_file.path, path)
58
+ ensure
59
+ temp_file.close
60
+ temp_file.unlink
61
+ end
62
+ end
63
+
64
+ def run_substitutions
65
+ load_data
66
+ @data.keys.each do |entry|
67
+ @data[entry].keys.each do |glob|
68
+ Chef::Log.info "Running substitutions for #{entry} -> #{glob}"
69
+ Dir[::File.join(backup_path, glob)].each do |file|
70
+ @data[entry][glob].each do |substitution|
71
+ search = Regexp.new(substitution['pattern'])
72
+ replace = substitution['replace'].dup
73
+ replace.gsub!(/\!COOKBOOK_VERSION\!/) { |m| "'" + cookbook_version_from_path(file) + "'" }
74
+ sub_in_file(file, search, replace)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,4 @@
1
+ module KnifeTidy
2
+ VERSION = '0.1.0'
3
+ MAJOR, MINOR, TINY = VERSION.split('.')
4
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-tidy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Miller
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '11.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '11.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aruba
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov-console
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: chef
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Report on stale Chef Server nodes and cookbooks and clean up data integrity
98
+ issues in a knife-ec-backup object based backup
99
+ email:
100
+ - jmiller@chef.io
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - Gemfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - conf/substitutions.json.example
111
+ - knife-tidy.gemspec
112
+ - lib/chef/knife/tidy_backup_clean.rb
113
+ - lib/chef/knife/tidy_base.rb
114
+ - lib/chef/knife/tidy_server_report.rb
115
+ - lib/chef/tidy_server.rb
116
+ - lib/chef/tidy_substitutions.rb
117
+ - lib/knife-tidy/version.rb
118
+ homepage: https://github.com/jeremymv2/knife-tidy
119
+ licenses:
120
+ - Apache License, v2.0
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 2.2.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.6.11
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Report on stale Chef Server nodes and cookbooks and clean up data integrity
142
+ issues in a knife-ec-backup object based backup
143
+ test_files: []