Class: CodeownerValidator::CodeOwners

Inherits:
Object
  • Object
show all
Includes:
UtilityHelper
Defined in:
lib/codeowner_validator/code_owners.rb

Overview

Public: Manages the interactions with the GitHub CODEOWNERS file. Information such as assignments, missing assignments, etc are retrieved though the usage of this class.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from UtilityHelper

#in_folder, #with_clean_env

Constructor Details

#initialize(repo_path:, **_args) ⇒ CodeOwners

Public: Returns a instance of the [CodeOwners] object

Parameters:

  • _args (Hash)

    A hash of arguments allowed for creation of a [CodeOwners] object

  • args (Hash)

    a customizable set of options



32
33
34
35
36
37
38
39
40
# File 'lib/codeowner_validator/code_owners.rb', line 32

def initialize(repo_path:, **_args)
  @repo_path = repo_path

  # initialize params to suppress warnings about instance variables not initialized
  @list = nil
  @codeowner_file = nil
  @codeowner_file_paths = nil
  @included_files = nil
end

Instance Attribute Details

#repo_pathObject (readonly)

Public: The absolute path the the repository for evaluation



16
17
18
# File 'lib/codeowner_validator/code_owners.rb', line 16

def repo_path
  @repo_path
end

Class Method Details

.persist!(repo_path:, **args) ⇒ Object

Public: Returns a instance of the [CodeOwners] object

Parameters:

  • args (Hash)

    A hash of arguments allowed for creation of a [CodeOwners] object

Options Hash (**args):

  • :repo_path (String)

    The absolute path to the repository to be evaluated



23
24
25
# File 'lib/codeowner_validator/code_owners.rb', line 23

def persist!(repo_path:, **args)
  new(repo_path: repo_path, **args)
end

Instance Method Details

#changes_to_analyze(from: 'HEAD', to: 'HEAD^') ⇒ Hash

Public: Returns a [Hash] of key/value pairs of relative file name to git status (‘A’, ‘M’, ‘D’) between two commits.

Examples:

{
  "config/tenants/cert-int/eds04.yml" => "M",
  "config/tenants/dev-int/edd03.yml" => "M"
}

Parameters:

  • from (String) (defaults to: 'HEAD')

    The staring point for analyzing. Defaults to ‘HEAD’

  • to (String) (defaults to: 'HEAD^')

    The end point for analyzing. Defaults to ‘HEAD^’

Returns:

  • (Hash)

    of key/value pairs of relative file name to git status (‘A’, ‘M’, ‘D’)



54
55
56
# File 'lib/codeowner_validator/code_owners.rb', line 54

def changes_to_analyze(from: 'HEAD', to: 'HEAD^')
  git.diff(from, to).name_status.select(&whitelist)
end

#codeowner_fileString

Public: Returns a [String] for the code owner file if it exists; otherwise, raise exception

Returns:

  • (String)

    the path to the codeowners file; otherwise, raise exception if not exists



226
227
228
229
230
231
232
233
234
235
# File 'lib/codeowner_validator/code_owners.rb', line 226

def codeowner_file
  return @codeowner_file if @codeowner_file

  codeowner_file_paths.each do |path|
    current_file_path = File.join(repo_path, path)
    return current_file_path if File.exist?(current_file_path)
  end

  raise "Unable to locate a code owners file located [#{codeowner_file_paths.join(',')}]"
end

#defined_owner?(file) ⇒ true

Public: Returns <true> if there is a defined owner for a given file

Parameters:

  • file (String)

    The file to search if there is an owner assigned

Returns:

  • (true)

    if found; otherwise, <false>



204
205
206
207
208
209
210
211
212
# File 'lib/codeowner_validator/code_owners.rb', line 204

def defined_owner?(file)
  main_group.find do |line|
    next unless line.pattern?

    return true if line.match_file?(file)
  end

  false
end

#duplicated_patternsHash

Public: Returns a [Hash] of keyed item to array of patterns that are duplicated within the code owners file

Example:

"config/domains/cert-int/CaseCartCoordinator_CERTIFICATION.yml": [
  {Codeowners::Checker::Group::Pattern,
  <span class='object_link'><a href="/gems/codeowner_validator/Codeowners/Checker/Group/Pattern" title="Codeowners::Checker::Group::Pattern (class)">Codeowners::Checker::Group::Pattern</a></span>
]

}

Returns:

  • (Hash)

    of keyed [String] patterns to an [Array] of [Pattern]s



134
135
136
# File 'lib/codeowner_validator/code_owners.rb', line 134

def duplicated_patterns
  list.select { |l| l.pattern? }.group_by { |e| e.pattern }.select { |_k, v| v.size > 1 }
end

#find_by_owner(owner) ⇒ Array

Public: Returns the lines associated to a specific owner

Parameters:

  • owner (String)

    The owner to search for patterns

Returns:

  • (Array)

    of <Codeowners::Checker::Group::Pattern> objects



192
193
194
195
196
197
198
# File 'lib/codeowner_validator/code_owners.rb', line 192

def find_by_owner(owner)
  main_group.find.select do |line|
    next unless line.pattern?

    line.owner == owner
  end
end

#included_filesArray

Public: Return all relative paths to files for evaluation if to be owned by a set of code owners

Returns:

  • (Array)

    of relative paths to files that are to be included within the code owner evaluation



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/codeowner_validator/code_owners.rb', line 141

def included_files
  return @included_files if @included_files

  @included_files = []
  in_folder repo_path do
    Dir.glob(File.join(Dir.pwd, '**/*')) do |f|
      p = Pathname.new(f)
      # only return files for evaluation
      next unless p.file?

      # in order to properly match, must evaluate relative to the repo location that is
      # being evaluated.  absolute paths of the files do not match with relative matches
      relative_path_to_file = p.relative_path_from(Dir.pwd)
      @included_files << relative_path_to_file.to_s if whitelist.whitelisted?(relative_path_to_file)
    end
  end

  @included_files
end

#invalid_reference_linesArray

Public: Return a list of files from the codeowners file that are noted but do not exist

Returns:

  • (Array)

    of <Codeowners::Checker::Group::UnrecognizedLine>s or <Codeowners::Checker::Group::Pattern>s



114
115
116
117
118
119
120
121
# File 'lib/codeowner_validator/code_owners.rb', line 114

def invalid_reference_lines
  list.select do |line|
    next unless line.pattern? || line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)

    filename = line.pattern? ? line.pattern : line.to_file
    File.exist?(File.join(repo_path, filename)) == false
  end
end

#main_groupArray

Public: Returns an [Array] of [Codeowners::Checker::Group] objects indicating the grouping of code owners

of code owners

Returns:

  • (Array)

    of [Codeowners::Checker::Group] objects indicating the grouping



219
220
221
# File 'lib/codeowner_validator/code_owners.rb', line 219

def main_group
  @main_group ||= ::Codeowners::Checker::Group.parse(list)
end

#missing_assignmentsArray

Public: Return a list of files from the repository that do not have an owner assigned

Returns:

  • (Array)

    of files from the repository that are missing an assignment



98
99
100
# File 'lib/codeowner_validator/code_owners.rb', line 98

def missing_assignments
  @missing_assignments ||= included_files.reject(&method(:defined_owner?))
end

#pattern_has_files(pattern) ⇒ Hash

Public: Returns a [Hash] of key/value pairs of relative file name to git details for a supplied pattern

Examples:

pattern_has_files('config/tenants/dev')
{
  {
    "config/tenants/dev/64dev.yml" => {
      path: "config/tenants/dev/64dev.yml",
      mode_index: "100644",
      sha_index: "598b2193b22bc006ff000e3a51f6805b336ebed8",
      stage: "0"
    },
    "config/tenants/dev/deveng.yml" => {
      path: "config/tenants/dev/deveng.yml",
      mode_index: "100644",
      sha_index: "ee63d8c6e9ae7f432aafa7bd3436fae222cb3f5c",
      stage: "0"
    }
  }
}

Parameters:

  • pattern (String)

    The pattern to search for files within the repository

Returns:

  • (Hash)

    of key/value pairs of relative file name to git details for a supplied pattern



91
92
93
# File 'lib/codeowner_validator/code_owners.rb', line 91

def pattern_has_files(pattern)
  git.ls_files(pattern.gsub(/^\//, '')).reject(&whitelist).any?
end

#patterns_by_ownerHash

Public: Returns the patterns utilized with an array association to those patterns

Example Response:

"@orion-delivery/delivery-team": [
  "*"
],
"@orion-delivery/orion-delivery-ets": [
  "config/domains/production/**/*",
  "config/domains/sandbox/**/*"
],
"@orion-delivery/orion-shells": [
  "config/feature_definitions/authn-android-enable_biometric_unlock.yml",
  "config/domains/cert-int/IONServer_CERTIFICATION.yml"
]

Returns:

  • (Hash)

    The patterns keyed by the team with an array of associations



179
180
181
182
183
184
185
186
# File 'lib/codeowner_validator/code_owners.rb', line 179

def patterns_by_owner
  @patterns_by_owner ||=
    main_group.each_with_object(hash_of_arrays) do |line, patterns_by_owner|
      next unless line.pattern?

      line.owners.each { |owner| patterns_by_owner[owner] << line.pattern.gsub(/^\//, '') }
    end
end

#unrecognized_assignmentsArray

Public: Return a list of files from the codowners file that are missing the owner assignment

Returns:

  • (Array)

    of unrecognized lines from the codeowners file missing owner assignment



105
106
107
108
109
# File 'lib/codeowner_validator/code_owners.rb', line 105

def unrecognized_assignments
  list.select do |line|
    line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
  end
end

#useless_patternArray

Public: Returns an [Array] of patterns that have no files associated to them

Returns:

  • (Array)
    Array

    of patterns that have no files associated to them



61
62
63
64
65
66
# File 'lib/codeowner_validator/code_owners.rb', line 61

def useless_pattern
  @useless_pattern ||=
    list.select do |line|
      line.pattern? && !pattern_has_files(line.pattern)
    end
end

#whitelisted?(file) ⇒ true|false

Public: Returns <true> if the provided file is deemed whitelisted per the configuration; otherwise, <false>

Returns:

  • (true|false)

    if the provided file is deemed whitelisted per the configuration



241
242
243
# File 'lib/codeowner_validator/code_owners.rb', line 241

def whitelisted?(file)
  whitelist.whitelisted?(file)
end