codeowner_validator 0.1.1
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 +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/ISSUE_TEMPLATE/config.yml +6 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- data/.github/pull_request_template.md +32 -0
- data/.github/workflows/cd.yml +53 -0
- data/.github/workflows/ci.yml +39 -0
- data/.gitignore +23 -0
- data/.rspec +4 -0
- data/.rubocop.yml +926 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.vscode/launch.json +53 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +40 -0
- data/CONTRIBUTORS.md +3 -0
- data/Gemfile +20 -0
- data/LICENSE +205 -0
- data/NOTICE +13 -0
- data/README.md +160 -0
- data/Rakefile +8 -0
- data/bin/bundle-audit +29 -0
- data/bin/bundler-audit +29 -0
- data/bin/codeowner_validator +8 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/codeowner_validator.gemspec +41 -0
- data/lib/codeowner_validator/cli/validator_cli.rb +64 -0
- data/lib/codeowner_validator/code_owners.rb +308 -0
- data/lib/codeowner_validator/common/command.rb +40 -0
- data/lib/codeowner_validator/common/logging.rb +86 -0
- data/lib/codeowner_validator/common/tasks/base.rb +62 -0
- data/lib/codeowner_validator/group/comment/error.rb +21 -0
- data/lib/codeowner_validator/group/comment/info.rb +21 -0
- data/lib/codeowner_validator/group/comment/verbose.rb +21 -0
- data/lib/codeowner_validator/group/comment/warn.rb +21 -0
- data/lib/codeowner_validator/group/comment.rb +55 -0
- data/lib/codeowner_validator/helpers/config_helper.rb +73 -0
- data/lib/codeowner_validator/helpers/utility_helper.rb +30 -0
- data/lib/codeowner_validator/lists/whitelist.rb +145 -0
- data/lib/codeowner_validator/tasks/duplicate_checker.rb +32 -0
- data/lib/codeowner_validator/tasks/file_exists_checker.rb +35 -0
- data/lib/codeowner_validator/tasks/missing_assignment_checker.rb +32 -0
- data/lib/codeowner_validator/tasks/syntax_checker.rb +32 -0
- data/lib/codeowner_validator/validator.rb +76 -0
- data/lib/codeowner_validator/version.rb +11 -0
- data/lib/codeowner_validator.rb +32 -0
- data/lib/codeowners/checker/group/line.rb +12 -0
- metadata +204 -0
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'codeowner_validator/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'codeowner_validator'
|
9
|
+
spec.version = CodeownerValidator::VERSION
|
10
|
+
spec.authors = ['Greg Howdeshell']
|
11
|
+
spec.email = ['greg.howdeshell@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Write a short summary, because RubyGems requires one.'
|
14
|
+
spec.description = <<~DESC
|
15
|
+
GitHub CODEOWNERS validator
|
16
|
+
DESC
|
17
|
+
spec.homepage = 'https://github.com/cerner/codeowner_validator'
|
18
|
+
spec.license = 'Apache-2.0'
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files =
|
23
|
+
Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)\//) }
|
25
|
+
end
|
26
|
+
spec.bindir = 'bin'
|
27
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.required_ruby_version = '>= 2.6.2'
|
31
|
+
|
32
|
+
spec.add_dependency 'rainbow', '>= 2.0', '< 4.0.0'
|
33
|
+
spec.add_dependency 'thor', '~> 0.19'
|
34
|
+
|
35
|
+
spec.add_dependency 'tty-prompt', '~> 0.12'
|
36
|
+
spec.add_dependency 'tty-spinner', '~> 0.4'
|
37
|
+
spec.add_dependency 'tty-table', '~> 0.8'
|
38
|
+
|
39
|
+
spec.add_dependency 'codeowners-checker', '~> 1.1'
|
40
|
+
spec.add_dependency 'git', '~> 1.0'
|
41
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/validator'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
# Public: Class utilized for housing all validator executions
|
7
|
+
class ValidatorCLI < Thor
|
8
|
+
REPO_PATH_DESC = 'Absolute path to repository location for evaluation'
|
9
|
+
VERBOSE_DESC = 'Verbose output'
|
10
|
+
|
11
|
+
default_task :validate
|
12
|
+
desc '', 'validates the codeowners file'
|
13
|
+
|
14
|
+
method_option :repo_path, aliases: %w[-p --repo-path], desc: REPO_PATH_DESC
|
15
|
+
method_option :verbose, aliases: %w[-v --verbose], desc: VERBOSE_DESC, type: :boolean, default: false
|
16
|
+
# Public: Entry point execution to being the codeowner validation
|
17
|
+
def validate
|
18
|
+
get_user_input(options)
|
19
|
+
|
20
|
+
validator = Validator.new options
|
21
|
+
validator.validate
|
22
|
+
end
|
23
|
+
|
24
|
+
no_commands do
|
25
|
+
# combination of options from input and environment variables
|
26
|
+
def options
|
27
|
+
return @new_options if @new_options
|
28
|
+
|
29
|
+
original_options = super
|
30
|
+
|
31
|
+
# add any environment variables as overrides
|
32
|
+
@new_options =
|
33
|
+
{}.tap do |h|
|
34
|
+
original_options.each do |key, value|
|
35
|
+
h[key.to_sym] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
h[:verbose] = ENV['VERBOSE'] unless ENV['VERBOSE'].nil?
|
39
|
+
h[:repo_path] = ENV['REPO_PATH'] unless ENV['REPO_PATH'].nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
# setup initial configuration
|
43
|
+
CodeownerValidator.configure! @new_options
|
44
|
+
|
45
|
+
@new_options
|
46
|
+
end
|
47
|
+
|
48
|
+
# steps needed to ask for input that is deemed missing
|
49
|
+
def get_user_input(options = {})
|
50
|
+
return if options[:repo_path]
|
51
|
+
|
52
|
+
ConfigHelper.ask(
|
53
|
+
ident: :repo_path,
|
54
|
+
prompt: REPO_PATH_DESC,
|
55
|
+
required: true,
|
56
|
+
default: Dir.pwd
|
57
|
+
)
|
58
|
+
|
59
|
+
# set response on the options hash
|
60
|
+
options[:repo_path] = CodeownerValidator.repo_path
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require_relative 'helpers/utility_helper'
|
5
|
+
require 'codeowner_validator/lists/whitelist'
|
6
|
+
require 'codeowners/checker/group'
|
7
|
+
require_relative '../codeowners/checker/group/line'
|
8
|
+
|
9
|
+
# rubocop:disable Style/ImplicitRuntimeError
|
10
|
+
module CodeownerValidator
|
11
|
+
# Public: Manages the interactions with the GitHub CODEOWNERS file. Information such as
|
12
|
+
# assignments, missing assignments, etc are retrieved though the usage of this class.
|
13
|
+
class CodeOwners
|
14
|
+
include UtilityHelper
|
15
|
+
|
16
|
+
# Public: The absolute path the the repository for evaluation
|
17
|
+
attr_reader :repo_path
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Public: Returns a instance of the [CodeOwners] object
|
21
|
+
#
|
22
|
+
# @param [Hash] args A hash of arguments allowed for creation of a [CodeOwners] object
|
23
|
+
# @option args [String] :repo_path The absolute path to the repository to be evaluated
|
24
|
+
def persist!(repo_path:, **args)
|
25
|
+
new(repo_path: repo_path, **args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Returns a instance of the [CodeOwners] object
|
30
|
+
#
|
31
|
+
# @param [Hash] _args A hash of arguments allowed for creation of a [CodeOwners] object
|
32
|
+
# @option args [String] :repo_path The absolute path to the repository to be evaluated
|
33
|
+
def initialize(repo_path:, **_args)
|
34
|
+
@repo_path = repo_path
|
35
|
+
|
36
|
+
# initialize params to suppress warnings about instance variables not initialized
|
37
|
+
@list = nil
|
38
|
+
@codeowner_file = nil
|
39
|
+
@codeowner_file_paths = nil
|
40
|
+
@included_files = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Returns a [Hash] of key/value pairs of relative file name to git status ('A', 'M', 'D')
|
44
|
+
# between two commits.
|
45
|
+
#
|
46
|
+
# @param [String] from The staring point for analyzing. Defaults to 'HEAD'
|
47
|
+
# @param [String] to The end point for analyzing. Defaults to 'HEAD^'
|
48
|
+
# @return [Hash] of key/value pairs of relative file name to git status ('A', 'M', 'D')
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# {
|
52
|
+
# "config/tenants/cert-int/eds04.yml" => "M",
|
53
|
+
# "config/tenants/dev-int/edd03.yml" => "M"
|
54
|
+
# }
|
55
|
+
def changes_to_analyze(from: 'HEAD', to: 'HEAD^')
|
56
|
+
git.diff(from, to).name_status.select(&whitelist)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Returns an [Array] of patterns that have no files associated to them
|
60
|
+
#
|
61
|
+
# @return [Array] [Array] of patterns that have no files associated to them
|
62
|
+
def useless_pattern
|
63
|
+
@useless_pattern ||=
|
64
|
+
list.select do |line|
|
65
|
+
line.pattern? && !pattern_has_files(line.pattern)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Returns a [Hash] of key/value pairs of relative file name to git details for a supplied pattern
|
70
|
+
#
|
71
|
+
# @param [String] pattern The pattern to search for files within the repository
|
72
|
+
# @return [Hash] of key/value pairs of relative file name to git details for a supplied pattern
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# pattern_has_files('config/tenants/dev')
|
76
|
+
# {
|
77
|
+
# {
|
78
|
+
# "config/tenants/dev/64dev.yml" => {
|
79
|
+
# path: "config/tenants/dev/64dev.yml",
|
80
|
+
# mode_index: "100644",
|
81
|
+
# sha_index: "598b2193b22bc006ff000e3a51f6805b336ebed8",
|
82
|
+
# stage: "0"
|
83
|
+
# },
|
84
|
+
# "config/tenants/dev/deveng.yml" => {
|
85
|
+
# path: "config/tenants/dev/deveng.yml",
|
86
|
+
# mode_index: "100644",
|
87
|
+
# sha_index: "ee63d8c6e9ae7f432aafa7bd3436fae222cb3f5c",
|
88
|
+
# stage: "0"
|
89
|
+
# }
|
90
|
+
# }
|
91
|
+
# }
|
92
|
+
def pattern_has_files(pattern)
|
93
|
+
git.ls_files(pattern.gsub(/^\//, '')).reject(&whitelist).any?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Return a list of files from the repository that do not have an owner assigned
|
97
|
+
#
|
98
|
+
# @return [Array] of files from the repository that are missing an assignment
|
99
|
+
def missing_assignments
|
100
|
+
@missing_assignments ||= included_files.reject(&method(:defined_owner?))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Return a list of files from the codowners file that are missing the owner assignment
|
104
|
+
#
|
105
|
+
# @return [Array] of unrecognized lines from the codeowners file missing owner assignment
|
106
|
+
def unrecognized_assignments
|
107
|
+
list.select do |line|
|
108
|
+
line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Public: Return a list of files from the codeowners file that are noted but do not exist
|
113
|
+
#
|
114
|
+
# @return [Array] of <Codeowners::Checker::Group::UnrecognizedLine>s or <Codeowners::Checker::Group::Pattern>s
|
115
|
+
def invalid_reference_lines
|
116
|
+
list.select do |line|
|
117
|
+
next unless line.pattern? || line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
|
118
|
+
|
119
|
+
filename = line.pattern? ? line.pattern : line.to_file
|
120
|
+
File.exist?(File.join(repo_path, filename)) == false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Public: Returns a [Hash] of keyed item to array of patterns that are duplicated within the code owners file
|
125
|
+
#
|
126
|
+
# @return [Hash] of keyed [String] patterns to an [Array] of [Pattern]s
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
# {
|
130
|
+
# "config/domains/cert-int/CaseCartCoordinator_CERTIFICATION.yml": [
|
131
|
+
# {Codeowners::Checker::Group::Pattern},
|
132
|
+
# {Codeowners::Checker::Group::Pattern}
|
133
|
+
# ]
|
134
|
+
# }
|
135
|
+
def duplicated_patterns
|
136
|
+
list.select { |l| l.pattern? }.group_by { |e| e.pattern }.select { |_k, v| v.size > 1 }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Return all relative paths to files for evaluation if to be owned by a set of code owners
|
140
|
+
#
|
141
|
+
# @return [Array] of relative paths to files that are to be included within the code owner evaluation
|
142
|
+
def included_files
|
143
|
+
return @included_files if @included_files
|
144
|
+
|
145
|
+
@included_files = []
|
146
|
+
in_folder repo_path do
|
147
|
+
Dir.glob(File.join(Dir.pwd, '**/*')) do |f|
|
148
|
+
p = Pathname.new(f)
|
149
|
+
# only return files for evaluation
|
150
|
+
next unless p.file?
|
151
|
+
|
152
|
+
# in order to properly match, must evaluate relative to the repo location that is
|
153
|
+
# being evaluated. absolute paths of the files do not match with relative matches
|
154
|
+
relative_path_to_file = p.relative_path_from(Dir.pwd)
|
155
|
+
@included_files << relative_path_to_file.to_s if whitelist.whitelisted?(relative_path_to_file)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
@included_files
|
160
|
+
end
|
161
|
+
|
162
|
+
# Public: Returns the patterns utilized with an array association to those patterns
|
163
|
+
#
|
164
|
+
# @return [Hash] The patterns keyed by the team with an array of associations
|
165
|
+
#
|
166
|
+
# Example Response:
|
167
|
+
# {
|
168
|
+
# "@orion-delivery/delivery-team": [
|
169
|
+
# "*"
|
170
|
+
# ],
|
171
|
+
# "@orion-delivery/orion-delivery-ets": [
|
172
|
+
# "config/domains/production/**/*",
|
173
|
+
# "config/domains/sandbox/**/*"
|
174
|
+
# ],
|
175
|
+
# "@orion-delivery/orion-shells": [
|
176
|
+
# "config/feature_definitions/authn-android-enable_biometric_unlock.yml",
|
177
|
+
# "config/domains/cert-int/IONServer_CERTIFICATION.yml"
|
178
|
+
# ]
|
179
|
+
# }
|
180
|
+
def patterns_by_owner
|
181
|
+
@patterns_by_owner ||=
|
182
|
+
main_group.each_with_object(hash_of_arrays) do |line, patterns_by_owner|
|
183
|
+
next unless line.pattern?
|
184
|
+
|
185
|
+
line.owners.each { |owner| patterns_by_owner[owner] << line.pattern.gsub(/^\//, '') }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Public: Returns the lines associated to a specific owner
|
190
|
+
#
|
191
|
+
# @param [String] owner The owner to search for patterns
|
192
|
+
# @return [Array] of <Codeowners::Checker::Group::Pattern> objects
|
193
|
+
def find_by_owner(owner)
|
194
|
+
main_group.find.select do |line|
|
195
|
+
next unless line.pattern?
|
196
|
+
|
197
|
+
line.owner == owner
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Public: Returns <true> if there is a defined owner for a given file
|
202
|
+
#
|
203
|
+
# @param [String] file The file to search if there is an owner assigned
|
204
|
+
# @return <true> if found; otherwise, <false>
|
205
|
+
def defined_owner?(file)
|
206
|
+
main_group.find do |line|
|
207
|
+
next unless line.pattern?
|
208
|
+
|
209
|
+
return true if line.match_file?(file)
|
210
|
+
end
|
211
|
+
|
212
|
+
false
|
213
|
+
end
|
214
|
+
|
215
|
+
# Public: Returns an [Array] of [Codeowners::Checker::Group] objects indicating
|
216
|
+
# the grouping of code owners
|
217
|
+
#
|
218
|
+
# @return [Array] of [Codeowners::Checker::Group] objects indicating the grouping
|
219
|
+
# of code owners
|
220
|
+
def main_group
|
221
|
+
@main_group ||= ::Codeowners::Checker::Group.parse(list)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Public: Returns a [String] for the code owner file if it exists; otherwise, raise exception
|
225
|
+
#
|
226
|
+
# @return [String] the path to the codeowners file; otherwise, raise exception if not exists
|
227
|
+
def codeowner_file
|
228
|
+
return @codeowner_file if @codeowner_file
|
229
|
+
|
230
|
+
codeowner_file_paths.each do |path|
|
231
|
+
current_file_path = File.join(repo_path, path)
|
232
|
+
return current_file_path if File.exist?(current_file_path)
|
233
|
+
end
|
234
|
+
|
235
|
+
raise "Unable to locate a code owners file located [#{codeowner_file_paths.join(',')}]"
|
236
|
+
end
|
237
|
+
|
238
|
+
# Public: Returns <true> if the provided file is deemed whitelisted per the configuration;
|
239
|
+
# otherwise, <false>
|
240
|
+
#
|
241
|
+
# @return [true|false] if the provided file is deemed whitelisted per the configuration
|
242
|
+
def whitelisted?(file)
|
243
|
+
whitelist.whitelisted?(file)
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# returns the git object representation of the repo provided
|
249
|
+
def git
|
250
|
+
@git ||= Git.open(@repo_path, log: Logger.new(IO::NULL))
|
251
|
+
end
|
252
|
+
|
253
|
+
# returns the configured whitelist of patterns to include
|
254
|
+
def whitelist
|
255
|
+
@whitelist ||= ::CodeownerValidator::Lists::Whitelist.new(repo_path: repo_path)
|
256
|
+
end
|
257
|
+
|
258
|
+
# locations to search for the codeowners file
|
259
|
+
def codeowner_file_paths
|
260
|
+
return @codeowner_file_paths if @codeowner_file_paths
|
261
|
+
|
262
|
+
@codeowner_file_paths = %w[CODEOWNERS docs/CODEOWNERS .github/CODEOWNERS]
|
263
|
+
|
264
|
+
# allow customization of the locations to search for the file
|
265
|
+
if ENV['CODEOWNER_FILE_PATHS']
|
266
|
+
ENV['CODEOWNER_FILE_PATHS']&.split(',')&.each(&:strip!)&.each do |str|
|
267
|
+
@codeowner_file_paths << str
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
@codeowner_file_paths
|
272
|
+
end
|
273
|
+
|
274
|
+
# constructs a new hash-to-array mapping
|
275
|
+
def hash_of_arrays
|
276
|
+
Hash.new { |h, k| h[k] = [] }
|
277
|
+
end
|
278
|
+
|
279
|
+
# returns an array of built lines
|
280
|
+
def list
|
281
|
+
return @list if @list
|
282
|
+
|
283
|
+
@list =
|
284
|
+
content.each_with_index.map do |line, index|
|
285
|
+
l = build_line(line)
|
286
|
+
l.line_number = index + 1
|
287
|
+
l
|
288
|
+
end
|
289
|
+
|
290
|
+
@list.compact!
|
291
|
+
|
292
|
+
@list
|
293
|
+
end
|
294
|
+
|
295
|
+
# builds a group line identifier
|
296
|
+
def build_line(line)
|
297
|
+
::Codeowners::Checker::Group::Line.build(line)
|
298
|
+
end
|
299
|
+
|
300
|
+
# @return <Array> of lines chomped
|
301
|
+
def content
|
302
|
+
@content ||= ::File.readlines(codeowner_file).map(&:chomp)
|
303
|
+
rescue Errno::ENOENT
|
304
|
+
@content = []
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
# rubocop:enable Style/ImplicitRuntimeError
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/logging'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module CodeownerValidator
|
7
|
+
# Public: Module utilized for housing the interactions with the terminal
|
8
|
+
module Command
|
9
|
+
include ::CodeownerValidator::Logging
|
10
|
+
|
11
|
+
# Executes system commands
|
12
|
+
def run(*args, log: true)
|
13
|
+
output = []
|
14
|
+
status = nil
|
15
|
+
log_command(*args) if log
|
16
|
+
|
17
|
+
Open3.popen2e(*args) do |_stdin, stdout_and_stderr, wait_thr|
|
18
|
+
until (line = stdout_and_stderr.gets).nil?
|
19
|
+
output.push line
|
20
|
+
log_info line.chop
|
21
|
+
end
|
22
|
+
|
23
|
+
status = wait_thr.value
|
24
|
+
end
|
25
|
+
|
26
|
+
return if status.success?
|
27
|
+
|
28
|
+
message =
|
29
|
+
[].tap do |ar|
|
30
|
+
ar << "Status: #{status.exitstatus}"
|
31
|
+
# because some commands contain sensitive information (ie passwords),
|
32
|
+
# may not want to display the actual command
|
33
|
+
ar << "Command: #{args.join(' ')}" if log
|
34
|
+
end
|
35
|
+
|
36
|
+
log_error message.join(', ')
|
37
|
+
raise message.join(', ')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rainbow'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module CodeownerValidator
|
7
|
+
# Public: Mixin for adding methods needed for logging directly to the stdout
|
8
|
+
module Logging
|
9
|
+
# Internal: Reader for the logger attribute.
|
10
|
+
#
|
11
|
+
# Returns a Logger instance.
|
12
|
+
def logger
|
13
|
+
return @logger if @logger
|
14
|
+
|
15
|
+
# depending of the context, utilize existing rails logger if exists
|
16
|
+
@logger = rails_logger || default_logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Designation if to show verbose output
|
20
|
+
def log_verbose(*messages)
|
21
|
+
return unless verbose?
|
22
|
+
|
23
|
+
messages.flatten.each do |message|
|
24
|
+
logger.info Rainbow(message || yield).magenta.bright
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Displays the command message to the console
|
29
|
+
def log_command(*messages)
|
30
|
+
messages.flatten.each do |message|
|
31
|
+
logger.info Rainbow(message).bright
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Displays an informational message to the console
|
36
|
+
def log_info(*messages)
|
37
|
+
messages.flatten.each do |message|
|
38
|
+
logger.info Rainbow(message).blue.bright
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Displays a warning message to the console
|
43
|
+
def log_warn(*messages)
|
44
|
+
messages.flatten.each do |message|
|
45
|
+
logger.warn Rainbow(message).yellow
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Displays an error message to the console
|
50
|
+
def log_error(*messages)
|
51
|
+
messages.flatten.each do |message|
|
52
|
+
logger.error Rainbow(message).red.bold
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Displays the stderr output to the console
|
57
|
+
def log_stderr(*args)
|
58
|
+
args.each do |message|
|
59
|
+
logger.error message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Displays the current program running name
|
64
|
+
def program_name
|
65
|
+
@program_name ||= File.basename($PROGRAM_NAME)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Get the Rails logger if it's defined.
|
71
|
+
#
|
72
|
+
# @example Get Rails' logger.
|
73
|
+
# Loggable.rails_logger
|
74
|
+
#
|
75
|
+
# @return [ Logger ] The Rails logger.
|
76
|
+
def rails_logger
|
77
|
+
defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_logger
|
81
|
+
logger = ::Logger.new(STDOUT)
|
82
|
+
logger.level = ::Logger::INFO
|
83
|
+
logger
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/logging'
|
4
|
+
require 'codeowner_validator/common/command'
|
5
|
+
require 'codeowner_validator/helpers/utility_helper'
|
6
|
+
|
7
|
+
module CodeownerValidator
|
8
|
+
module Tasks
|
9
|
+
# Public: The tasks base class is used for defining the basis for task executions from the
|
10
|
+
# importer or merger executors.
|
11
|
+
class Base
|
12
|
+
include ::CodeownerValidator::Logging
|
13
|
+
include ::CodeownerValidator::Command
|
14
|
+
include ::CodeownerValidator::UtilityHelper
|
15
|
+
|
16
|
+
# Public: Initializing the task with provided arguments
|
17
|
+
# @param [Hash] _args The hash of arguments to utilize for the initialization
|
18
|
+
# @options _args [Boolean] :verbose Indicate if verbose output should be included
|
19
|
+
# @options _args [String] :repo_path The absolute path to the repo for evaluation
|
20
|
+
def initialize(verbose: false, repo_path:, **_args)
|
21
|
+
@verbose = verbose
|
22
|
+
@repo_path = repo_path
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Returns the summary of what the task is to accomplish. Expectation that each
|
26
|
+
# task will define its intent which overrides just the class name output.
|
27
|
+
def summary
|
28
|
+
self.class.name
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Executes task's commands
|
32
|
+
def execute
|
33
|
+
comments&.each { |c| log_error c.comment }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Executes all tasks and responds with an array of comments
|
37
|
+
#
|
38
|
+
# @return [Array] Returns an array of comments from the execution of the tasks to be utilized by the consumer
|
39
|
+
def comments; end
|
40
|
+
|
41
|
+
# Public: Returns the codeowner object associated to the repository selected
|
42
|
+
#
|
43
|
+
# @return [CodeownerValidator::CodeOwners] object associate to the repository selected
|
44
|
+
def codeowners
|
45
|
+
@codeowners ||= CodeOwners.new(repo_path: @repo_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def in_repo_folder(&block)
|
51
|
+
in_folder(@repo_path, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def verbose?
|
55
|
+
return CodeownerValidator.verbose if CodeownerValidator.respond_to?(:verbose)
|
56
|
+
|
57
|
+
env_verbose = %w[true yes].include? ENV['VERBOSE']&.downcase
|
58
|
+
@verbose || env_verbose
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|