class Dependabot::Linguist::DependabotFileValidator

Reads an existing dependabot file and determines how it should be updated to meet the suggested entried to the updates list coming from repository’s directories_per_ecosystem_validated_by_dependabot

Constants

CONFIG_FILE_PATH
YAML_FILE_PATH
YML_FILE_PATH

Public Class Methods

checking_exists(checking, exists) click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 98
def self.checking_exists(checking, exists)
  exists["package-ecosystem"] == checking[0] && exists["directory"] == checking[1]
end
flatten_ecodirs_to_ecodir(ecosystem_directories_map) click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 94
def self.flatten_ecodirs_to_ecodir(ecosystem_directories_map)
  ecosystem_directories_map.collect { |eco, dirs| dirs.collect { |dir| [eco, dir] } }.flatten(1)
end
new(repo_path, remove_undiscovered: false, update_existing: true, minimum_interval: "weekly", max_open_pull_requests_limit: 5, verbose: false) click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 12
def initialize(repo_path, remove_undiscovered: false, update_existing: true, minimum_interval: "weekly", max_open_pull_requests_limit: 5, verbose: false)
  @repo = Rugged::Repository.new(repo_path)
  @remove_undiscovered = remove_undiscovered
  @update_existing = update_existing
  @minimum_interval = minimum_interval
  @max_open_pull_requests_limit = [max_open_pull_requests_limit, 0].max
  @verbose = verbose
  @load_ecosystem_directories ||= nil
end

Public Instance Methods

commit_new_config() click to toggle source

The expected environment to run this final step in should have ‘git’ AND ‘gh’ available as commands to run, and calls out to a subshell to run them as set up by the environment that runs this, rather than requiring credentials being provided to this class.

# File lib/dependabot/linguist/dependabot_file_validator.rb, line 214
def commit_new_config
  new_branch = @repo.create_branch("dependabot-linguist_auto-config-update")
  in_repo = "cd #{@repo.path.delete_suffix("/.git/")} &&"
  `#{"#{in_repo} git checkout #{new_branch.name}"}`
  write_new_config
  `#{"#{in_repo} git add #{dependabot_file_path}"}`
  `#{"#{in_repo} git commit -m \"Auto update #{dependabot_file_path} -- dependabot-linguist\""}`
  `#{"#{in_repo} git push --set-upstream #{@repo.remotes["origin"].name} #{new_branch.name}"}`
  `#{"#{in_repo} gh pr create --fill"}`
end
config_drift() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 108
def config_drift
  confirm_config_version_is_valid
  @config_drift ||= {}.tap do |this|
    ecodir_list = self.class.flatten_ecodirs_to_ecodir(load_ecosystem_directories)
    this[ConfigDriftStatus::ALREADY_IN] = []
    this[ConfigDriftStatus::TO_BE_ADDED] = []
    this[ConfigDriftStatus::UNDISCOVERED] = []
    this.freeze
    ecodir_list.each do |checking_ecodir|
      next if ecodir_is_ignored(checking_ecodir[0], checking_ecodir[1])
      if !existing_config.empty? && !existing_config["updates"].nil?
        existed_ecodir = nil
        existing_config["updates"].each do |existing_ecodir|
          if self.class.checking_exists(checking_ecodir, existing_ecodir)
            puts "#{ConfigDriftStatus::ALREADY_IN}; {#{checking_ecodir[0]} @ #{checking_ecodir[1]}}" if @verbose
            this[ConfigDriftStatus::ALREADY_IN].append(checking_ecodir)
            existed_ecodir = existing_ecodir
            break # existing_ecodir
          end
        end
        # break to here
        next unless existed_ecodir.nil? # checking_ecodir
      end
      # If we didn't break -> next, then we've got a checking_ecodir
      # that we didn't find already present in the existing ecodirs.
      puts "#{ConfigDriftStatus::TO_BE_ADDED}; {#{checking_ecodir[0]} @ #{checking_ecodir[1]}}" if @verbose
      this[ConfigDriftStatus::TO_BE_ADDED].append(checking_ecodir)
    end
    if !existing_config.empty? && !existing_config["updates"].nil?
      existing_config["updates"].each do |existing_ecodir|
        existed_ecodir = nil
        ecodir_list.each do |checking_ecodir|
          break if ecodir_is_ignored(checking_ecodir[0], checking_ecodir[1])
          existed_ecodir = checking_ecodir if self.class.checking_exists(checking_ecodir, existing_ecodir)
          break unless existed_ecodir.nil?
        end
        if existed_ecodir.nil?
          puts "#{ConfigDriftStatus::UNDISCOVERED}; {#{existing_ecodir["package-ecosystem"]} @ #{existing_ecodir["directory"]}} that wasn't found by us!!" if @verbose
          this[ConfigDriftStatus::UNDISCOVERED].append([existing_ecodir["package-ecosystem"], existing_ecodir["directory"]])
        end
      end
    end
  end
end
confirm_config_version_is_valid() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 76
def confirm_config_version_is_valid
  raise StandardError("The existing config has a version other than 2") unless existing_config["version"] == 2
end
dependabot_file_path() click to toggle source

rubocop:disable Layout/IndentationWidth, Layout/ElseAlignment, Layout/EndAlignment

# File lib/dependabot/linguist/dependabot_file_validator.rb, line 30
def dependabot_file_path
  @dependabot_file_path ||= if @repo.blob_at(@repo.head.target_id, YML_FILE_PATH)
    # the yml extension is preferred by GitHub, so even though this
    # returns the same as the `else`, check it before YAML.
    YML_FILE_PATH
  elsif @repo.blob_at(@repo.head.target_id, YAML_FILE_PATH)
    YAML_FILE_PATH
  else
    @existing_config = { "version" => 2, "updates" => [] }
    YML_FILE_PATH
  end
end
ecodir_is_ignored(eco, dir) click to toggle source

Is a yaml config file exists that looks like

ignore:

directory:
  /path/to/somewhere:
  - some_ecosystem
ecosystem:
  some_other_ecosystem:
  - /path/to/somewhere_else

then both (some_ecosystem, “/path/to/somewhere”) and (some_other_ecosystem, “/path/to/somewhere_else”) should be “ignored” by this system.

# File lib/dependabot/linguist/dependabot_file_validator.rb, line 72
def ecodir_is_ignored(eco, dir)
  ((((meta_config["ignore"] || {})["directory"] || {})[dir] || []).any? eco) || ((((meta_config["ignore"] || {})["ecosystem"] || {})[eco] || []).any? dir)
end
existing_config() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 43
def existing_config
  dependabot_file_path # to = {} if the file doesn't exist or isn't committed.
  # @existing_config ||= YAML.load_file(File.join(@repo.path, dependabot_file_path))
  @existing_config ||= YAML.safe_load(@repo.blob_at(@repo.head.target_id, dependabot_file_path).content)
end
load_ecosystem_directories(incoming: @load_ecosystem_directories) click to toggle source

Expects an input that is the output of ::Dependabot::Linguist::Repository.new(~)‘s directories_per_ecosystem_validated_by_dependabot, which should be a map {“<package_ecosystem>” => [“<folder_path>”, …], …}

# File lib/dependabot/linguist/dependabot_file_validator.rb, line 83
def load_ecosystem_directories(incoming: @load_ecosystem_directories)
  @load_ecosystem_directories ||= nil
  if @load_ecosystem_directories == incoming
    @load_ecosystem_directories
  else
    @config_drift = nil
    @new_config = nil
    @load_ecosystem_directories = incoming
  end
end
meta_config() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 49
def meta_config
  @meta_config ||= if @repo.blob_at(@repo.head.target_id, CONFIG_FILE_PATH)
    YAML.safe_load(@repo.blob_at(@repo.head.target_id, CONFIG_FILE_PATH).content)
  else
    {}
  end
end
new_config() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 162
def new_config
  confirm_config_version_is_valid
  @new_config ||= YAML.safe_load(existing_config.to_yaml).tap do |this|
    this["updates"] = [] if this["updates"].nil?
    # If "remove_undiscovered" is set, then set this to reject any
    # updates that are in the list of those undiscovered. Removing
    # is not safe from inside each, so reject instead.
    this["updates"] = this["updates"].reject { |u| config_drift[ConfigDriftStatus::UNDISCOVERED].any? [u["package-ecosystem"], u["directory"]] } if @remove_undiscovered
    # Next, go through and update any existing.
    if @update_existing
      this["updates"].each do |existing_update|
        if config_drift[ConfigDriftStatus::ALREADY_IN].any? [existing_update["package-ecosystem"], existing_update["directory"]]
          # Confirm that the already present entry is good enough
          if existing_update["schedule"].is_a? Hash
            new_interval = parsed_schedule_interval(existing_update["schedule"]["interval"])
            existing_update["schedule"]["interval"] = new_interval
            # if it's not weekly anymore remove day if it's specified.
            if existing_update["schedule"]["interval"] != "weekly"
              existing_update["schedule"].delete("day")
            end
          else
            existing_update["schedule"] = { "interval" => parsed_schedule_interval("monthly") }
          end
          # Confirm the open-pull-requests-limit
          if existing_update["open-pull-requests-limit"]
            existing_update["open-pull-requests-limit"] = [existing_update["open-pull-requests-limit"], @max_open_pull_requests_limit].min
          else
            existing_update["open-pull-requests-limit"] = @max_open_pull_requests_limit
          end
          existing_update.delete("open-pull-requests-limit") if existing_update["open-pull-requests-limit"] == 5
        end
      end
    end
    config_drift[ConfigDriftStatus::TO_BE_ADDED].each do |tba|
      new_update = { "package-ecosystem" => tba[0], "directory" => tba[1] }
      new_update["schedule"] = { "interval" => parsed_schedule_interval("monthly") }
      new_update["open-pull-requests-limit"] = @max_open_pull_requests_limit if @max_open_pull_requests_limit != 5
      this["updates"].append(new_update)
    end
  end
end
parsed_schedule_interval(interval) click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 153
def parsed_schedule_interval(interval)
  intervals = ["daily", "weekly", "monthly"].freeze
  if intervals.any? @minimum_interval
    intervals[[intervals.find_index(@minimum_interval) || (intervals.length-1), intervals.find_index(interval) || (intervals.length-1)].min]
  else
    interval
  end
end
write_new_config() click to toggle source
# File lib/dependabot/linguist/dependabot_file_validator.rb, line 204
def write_new_config
  full_file_path = "#{@repo.path.delete_suffix("/.git/")}/#{dependabot_file_path}"
  FileUtils.mkdir_p File.dirname(full_file_path)
  File.open(full_file_path, "w") { |file| file.write(new_config.to_yaml) } if new_config != existing_config
end