app/shared/fastfile_peeker.rb

Summary

Maintainability
A
1 hr
Test Coverage
require_relative "fastfile_finder"
require_relative "github_handler"
require_relative "logging_module"

require "fastfile_parser"
require "digest"

module FastlaneCI
  # Object that encapsulates retrieving Fastfile information for a given repo.
  class FastfilePeeker
    include FastlaneCI::GitHubHandler
    include FastlaneCI::Logging

    def initialize(provider_credential: nil, notification_service: nil)
      @provider_credential = provider_credential
      @notification_service = notification_service
      @client = Octokit::Client.new(access_token: @provider_credential.api_token)
    end

    def fastfile(repo_config:, sha_or_branch:)
      fastfile_parser = fastfile_from_github(
        repo_full_name: repo_config.full_name,
        sha_or_branch: sha_or_branch
      )
      if fastfile_parser.nil?
        logger.debug("Checking out repo and searching for fastfile in #{repo_config.full_name}")
        fastfile_parser = fastfile_from_repo(repo_config: repo_config, branch: sha_or_branch)
      end
      return fastfile_parser
    end

    # For a given repo, and some state associated with it (a branch or a commit sha) retrieve the FastfileParser
    # instance and return it.
    # @param [GitRepo] git_repo
    # @param [String, nil] branch
    # @param [String, nil] sha
    # @return [Fastlane::FastfileParser]
    def fastfile_from_repo(repo_config: nil, branch: nil, sha: nil)
      git_repo = FastlaneCI::GitRepo.new(
        git_config: repo_config,
        local_folder: Dir.mktmpdir,
        provider_credential: @provider_credential,
        async_start: false,
        notification_service: @notification_service
      )
      git_repo.fetch

      if branch && !branch.empty?
        # This performs the checkout of the latest commit in the branch.
        git_repo.checkout_branch(branch: branch)
      elsif sha && !sha.empty?
        git_repo.chekout_commit(sha: sha)
      else
        raise "Invalid branch or sha were provided"
      end
      # To look up for the Fastfile in the given repo directory, we make use of the
      # FastfileFinder as a global utility to find a Fastfile given a repo or a root path.
      fastfile_path = FastfileFinder.find_fastfile_in_repo(repo: git_repo)
      if fastfile_path
        fastfile = Fastlane::FastfileParser.new(path: fastfile_path)
        return fastfile
      else
        raise "No Fastfile found in #{git_repo.local_folder}"
      end
    end

    private

    def fastfile_from_contents_map(contents_map)
      github_action(@client) do |_client|
        return nil if contents_map.nil?

        if contents_map
          content = contents_map[:content]
          return contents_map[:encoding] == "base64" ? Base64.decode64(content) : content
        end
        return nil
      end
    end

    def remote_paths(repo_full_name:, sha_or_branch:)
      github_action(@client) do
        begin
          logger.debug("Checking for fastfile in #{repo_full_name}")
          result = @client.tree(repo_full_name, sha_or_branch, { recursive: true })
          return result[:tree].map { |resource| resource[:path] } || []
        rescue Octokit::NotFound
          return []
        end
      end
    end

    def remote_file_contents_map(repo_full_name: nil, sha_or_branch:, path:)
      github_action(@client) do |client|
        begin
          logger.debug("Checking for fastfile in #{repo_full_name}/fastlane/Fastfile")
          contents_map = client.contents(repo_full_name, path: path, ref: sha_or_branch)
          return contents_map
        rescue Octokit::NotFound
          return nil
        end
      end
    end

    def fastfile_from_github(repo_full_name: nil, sha_or_branch:)
      paths = remote_paths(repo_full_name: repo_full_name, sha_or_branch: sha_or_branch)

      fastfile_path = FastfileFinder.find_prioritary_fastfile_path(paths: paths)

      contents_map = remote_file_contents_map(
        repo_full_name: repo_full_name,
        sha_or_branch: sha_or_branch,
        path: fastfile_path
      )
      contents = fastfile_from_contents_map(contents_map)

      return Fastlane::FastfileParser.new(file_content: contents)
    end
  end
end