require 'open-uri' DEFAULT_WHITELIST_URL = "https://raw.githubusercontent.com/mercadolibre/mobile-dependencies_whitelist/master/ios-whitelist.json" ERROR_MESSAGE = "Please check your dependencies" class AllowedDependency attr_accessor :name attr_accessor :version def initialize(name, version) @name = name @version = version end end module Pod class Command class Whitelist < Command self.summary = "Validate Podspec's dependencies against a whitelist of pods." self.description = <<-DESC Validate Podspec's dependencies against a whitelist of pods. DESC self.arguments = [ CLAide::Argument.new('config', false), CLAide::Argument.new('podspec', false), CLAide::Argument.new('fail-on-error', false) ] def self.options [ ['--config=CONFIG', 'Config file or URL for the blacklist'], ['--podspec=PODSPEC', 'Podspec file to be lint'], ['--fail-on-error', 'Raise an exception in case of error'] ].concat(super) end def initialize(argv) @whitelist_url = argv ? argv.option('config', DEFAULT_WHITELIST_URL) : DEFAULT_WHITELIST_URL @pospec_path = argv ? argv.option('podspec') : nil @fail_on_error = argv ? argv.flag?('fail-on-error') : false @failure = false super end def validate! help! "A whitelist file or URL is needed." unless @whitelist_url end def run whitelist = get_whitelist specifications = get_podspec_specifications if specifications.empty? UI.puts "No Podspec found".yellow return end specifications.map do |specification| validate_dependencies(JSON.parse(specification.to_json), whitelist) end show_error_message end def show_error_message return unless @failure if @fail_on_error raise Informative.new(ERROR_MESSAGE) else UI.puts ERROR_MESSAGE.red end end # Checks the dependencies the project contains are in the whitelist def validate_dependencies(podspec, whitelist, parentName = nil) pod_name = parentName ? "#{parentName}/#{podspec['name']}" : podspec['name'] UI.puts "Verifying dependencies in #{pod_name}" dependencies = podspec["dependencies"] ? podspec["dependencies"] : [] not_allowed = [] dependencies.each do |name, versions| # Skip subspec dependency next if parentName && name.start_with?("#{parentName}/") allowedDependency = whitelist.select { |item| name == item.name && (versions.empty? || !item.version || versions.grep(/#{item.version}/).any?) } if allowedDependency.empty? not_allowed.push("#{name} (#{versions.join(", ")})") next end end if not_allowed.any? UI.puts " Error: Found dependencies not allowed:".red not_allowed.each {|dependency| UI.puts " - #{dependency}".red} @failure = true else UI.puts " OK".green end # Validate subspecs dependencies if podspec["subspecs"] podspec["subspecs"].each do |subspec| validate_dependencies(subspec, whitelist, pod_name) end end end def get_whitelist begin open(@whitelist_url) { |io| buffer = io.read parse_whitelist(buffer) } rescue OpenURI::HTTPError => e status = e.io.status.join(' ') raise "Failed to fetch whitelist from '#{@whitelist_url}'.\n Error: #{status}" end end def parse_whitelist(raw_whitelist) json = JSON.parse(raw_whitelist) return json["whitelist"].map { |dependencyJson| AllowedDependency.new(dependencyJson["name"], dependencyJson["version"]) } end def get_podspec_specifications if @pospec_path return [Pod::Specification.from_file(@pospec_path)] end # Search .podspec in current directory podspecs = Dir.glob("./*.podspec") if podspecs.count == 0 # Search .podspec in parent directory. # Some projects has Podfile into a subdirectory ("Example"), and run "pod install" from there. podspecs = Dir.glob("../*.podspec") end return podspecs.map { |path| Pod::Specification.from_file(path) } end end end end