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

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



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

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



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

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



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

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’)



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

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



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

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>



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

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,
  Codeowners::Checker::Group::Pattern
]

}

Returns:

  • (Hash)

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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