From a152f8471facd8661358dac57979f55c9d1034d4 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Thu, 19 Sep 2024 20:44:15 -0700 Subject: [PATCH] Strict type `Dependabot::Terraform::RegistryClient` --- .../dependabot/terraform/registry_client.rb | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/terraform/lib/dependabot/terraform/registry_client.rb b/terraform/lib/dependabot/terraform/registry_client.rb index 087aabdebd..3a1c06c9de 100644 --- a/terraform/lib/dependabot/terraform/registry_client.rb +++ b/terraform/lib/dependabot/terraform/registry_client.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/dependency" @@ -12,29 +12,36 @@ module Terraform # Terraform::RegistryClient is a basic API client to interact with a # terraform registry: https://www.terraform.io/docs/registry/api.html class RegistryClient - ARCHIVE_EXTENSIONS = %w(.zip .tbz2 .tgz .txz).freeze + extend T::Sig + + ARCHIVE_EXTENSIONS = T.let(%w(.zip .tbz2 .tgz .txz).freeze, T::Array[String]) PUBLIC_HOSTNAME = "registry.terraform.io" + sig { params(hostname: String, credentials: T::Array[Dependabot::Credential]).void } def initialize(hostname: PUBLIC_HOSTNAME, credentials: []) @hostname = hostname - @tokens = credentials.each_with_object({}) do |item, memo| - memo[item["host"]] = item["token"] if item["type"] == "terraform_registry" - end + @tokens = T.let( + credentials.each_with_object({}) do |item, memo| + memo[item["host"]] = item["token"] if item["type"] == "terraform_registry" + end, + T::Hash[String, String] + ) end # rubocop:disable Metrics/PerceivedComplexity - # See https://www.terraform.io/docs/modules/sources.html#http-urls for - # details of how Terraform handle HTTP(S) sources for modules # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity + # See https://www.terraform.io/docs/modules/sources.html#http-urls for + # details of how Terraform handle HTTP(S) sources for modules + sig { params(raw_source: String).returns(T.nilable(String)) } def self.get_proxied_source(raw_source) return raw_source unless raw_source.start_with?("http") - uri = URI.parse(raw_source.split(%r{(?] # @raise [Dependabot::DependabotError] when the versions cannot be retrieved + sig { params(identifier: String).returns(T::Array[Dependabot::Terraform::Version]) } def all_provider_versions(identifier:) base_url = service_url_for("providers.v1") response = http_get!(URI.join(base_url, "#{identifier}/versions")) @@ -80,6 +88,7 @@ def all_provider_versions(identifier:) # "hashicorp/consul/aws" # @return [Array] # @raise [Dependabot::DependabotError] when the versions cannot be retrieved + sig { params(identifier: String).returns(T::Array[Dependabot::Terraform::Version]) } def all_module_versions(identifier:) base_url = service_url_for("modules.v1") response = http_get!(URI.join(base_url, "#{identifier}/versions")) @@ -97,8 +106,9 @@ def all_module_versions(identifier:) # @param dependency [Dependabot::Dependency] the dependency who's source # we're attempting to find # @return [nil, Dependabot::Source] + sig { params(dependency: Dependabot::Dependency).returns(T.nilable(Dependabot::Source)) } def source(dependency:) - type = dependency.requirements.first[:source][:type] + type = T.must(dependency.requirements.first)[:source][:type] base_url = service_url_for(service_key_for(type)) case type # https://www.terraform.io/internals/module-registry-protocol#download-source-code-for-a-specific-module-version @@ -130,6 +140,7 @@ def source(dependency:) # @param service_key [String] the service type described in https://www.terraform.io/docs/internals/remote-service-discovery.html#supported-services # @param return String # @raise [Dependabot::PrivateSourceAuthenticationFailure] when the service is not available + sig { params(service_key: String).returns(String) } def service_url_for(service_key) url_for(services.fetch(service_key)) rescue KeyError @@ -138,26 +149,35 @@ def service_url_for(service_key) private + sig { returns(String) } attr_reader :hostname + + sig { returns(T::Hash[String, String]) } attr_reader :tokens + sig { returns(T.class_of(Dependabot::Terraform::Version)) } def version_class Version end + sig { params(hostname: String).returns(T::Hash[String, String]) } def headers_for(hostname) token = tokens[hostname] token ? { "Authorization" => "Bearer #{token}" } : {} end + sig { returns(T::Hash[String, String]) } def services - @services ||= + @services ||= T.let( begin response = http_get(url_for("/.well-known/terraform.json")) response.status == 200 ? JSON.parse(response.body) : {} - end + end, + T.nilable(T::Hash[String, String]) + ) end + sig { params(type: String).returns(String) } def service_key_for(type) case type when "module", "modules", "registry" @@ -169,6 +189,7 @@ def service_key_for(type) end end + sig { params(url: T.any(String, URI::Generic)).returns(Excon::Response) } def http_get(url) Dependabot::RegistryClient.get( url: url.to_s, @@ -176,6 +197,7 @@ def http_get(url) ) end + sig { params(url: URI::Generic).returns(Excon::Response) } def http_get!(url) response = http_get(url) @@ -185,6 +207,7 @@ def http_get!(url) response end + sig { params(path: String).returns(String) } def url_for(path) uri = URI.parse(path) return uri.to_s if uri.scheme == "https" @@ -195,6 +218,7 @@ def url_for(path) uri.to_s end + sig { params(message: String).returns(Dependabot::DependabotError) } def error(message) Dependabot::DependabotError.new(message) end