Skip to content

Commit

Permalink
feat(gapic-common): Honor universe domain in stubs (#1008)
Browse files Browse the repository at this point in the history
  • Loading branch information
dazuma authored Dec 11, 2023
1 parent 72875f0 commit c07eed8
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 19 deletions.
33 changes: 24 additions & 9 deletions gapic-common/lib/gapic/grpc/service_stub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
require "gapic/grpc/service_stub/rpc_call"
require "gapic/grpc/service_stub/channel"
require "gapic/grpc/service_stub/channel_pool"

require "gapic/universe_domain_concerns"

module Gapic
##
Expand All @@ -31,14 +31,21 @@ module Gapic
# @return [Gapic::ServiceStub::ChannelPool] The instance of the ChannelPool class.
#
class ServiceStub
include UniverseDomainConcerns

attr_reader :grpc_stub
attr_reader :channel_pool

##
# Creates a Gapic gRPC stub object.
#
# @param grpc_stub_class [Class] gRPC stub class to create a new instance of.
# @param endpoint [String] The endpoint of the API.
# @param endpoint [String] The endpoint of the API. Overrides any endpoint_template.
# @param endpoint_template [String] The endpoint of the API, where the
# universe domain component of the hostname is marked by the string in
# the constant {UniverseDomainConcerns::ENDPOINT_SUBSTITUTION}.
# @param universe_domain [String] The universe domain in which calls should
# be made. Defaults to `googleapis.com`.
# @param credentials [Google::Auth::Credentials, Signet::OAuth2::Client, String, Hash, Proc,
# ::GRPC::Core::Channel, ::GRPC::Core::ChannelCredentials] Provides the means for authenticating requests made by
# the client. This parameter can be many types:
Expand All @@ -58,24 +65,32 @@ class ServiceStub
# @param channel_pool_config [::Gapic::ServiceStub:ChannelPool::Configuration] The configuration for channel
# pool. This argument will raise error when `credentials` is provided as a `::GRPC::Core::Channel`.
#
def initialize grpc_stub_class, endpoint:, credentials:, channel_args: nil,
interceptors: nil, channel_pool_config: nil
def initialize grpc_stub_class,
credentials:,
endpoint: nil,
endpoint_template: nil,
universe_domain: nil,
channel_args: nil,
interceptors: nil,
channel_pool_config: nil
raise ArgumentError, "grpc_stub_class is required" if grpc_stub_class.nil?
raise ArgumentError, "endpoint is required" if endpoint.nil?
raise ArgumentError, "credentials is required" if credentials.nil?

setup_universe_domain universe_domain: universe_domain,
endpoint: endpoint,
endpoint_template: endpoint_template,
credentials: credentials

@channel_pool = nil
@grpc_stub = nil
channel_args = Hash channel_args
interceptors = Array interceptors


if channel_pool_config && channel_pool_config.channel_count > 1
create_channel_pool grpc_stub_class, endpoint: endpoint, credentials: credentials,
create_channel_pool grpc_stub_class, endpoint: self.endpoint, credentials: self.credentials,
channel_args: channel_args, interceptors: interceptors,
channel_pool_config: channel_pool_config
else
create_grpc_stub grpc_stub_class, endpoint: endpoint, credentials: credentials,
create_grpc_stub grpc_stub_class, endpoint: self.endpoint, credentials: self.credentials,
channel_args: channel_args, interceptors: interceptors
end
end
Expand Down
33 changes: 25 additions & 8 deletions gapic-common/lib/gapic/rest/client_stub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

require "googleauth"
require "gapic/rest/faraday_middleware"
require "gapic/universe_domain_concerns"
require "faraday/retry"

module Gapic
Expand All @@ -26,9 +27,16 @@ module Rest
# - store credentials and add auth information to the request
#
class ClientStub
include UniverseDomainConcerns

##
# Initializes with an endpoint and credentials
# @param endpoint [String] an endpoint for the service that this stub will send requests to
# @param endpoint [String] The endpoint of the API. Overrides any endpoint_template.
# @param endpoint_template [String] The endpoint of the API, where the
# universe domain component of the hostname is marked by the string in
# the constant {UniverseDomainConcerns::ENDPOINT_SUBSTITUTION}.
# @param universe_domain [String] The universe domain in which calls
# should be made. Defaults to `googleapis.com`.
# @param credentials [Google::Auth::Credentials]
# Credentials to send with calls in form of a googleauth credentials object.
# (see the [googleauth docs](https://googleapis.dev/ruby/googleauth/latest/index.html))
Expand All @@ -42,19 +50,28 @@ class ClientStub
#
# @yield [Faraday::Connection]
#
def initialize endpoint:, credentials:, numeric_enums: false, raise_faraday_errors: true
@endpoint = endpoint
@endpoint = "https://#{endpoint}" unless /^https?:/.match? endpoint
@endpoint = @endpoint.sub %r{/$}, ""
def initialize credentials:,
endpoint: nil,
endpoint_template: nil,
universe_domain: nil,
numeric_enums: false,
raise_faraday_errors: true
setup_universe_domain universe_domain: universe_domain,
endpoint: endpoint,
endpoint_template: endpoint_template,
credentials: credentials

endpoint_url = self.endpoint
endpoint_url = "https://#{endpoint_url}" unless /^https?:/.match? endpoint_url
endpoint_url = endpoint_url.sub %r{/$}, ""

@credentials = credentials
@numeric_enums = numeric_enums

@raise_faraday_errors = raise_faraday_errors

@connection = Faraday.new url: @endpoint do |conn|
@connection = Faraday.new url: endpoint_url do |conn|
conn.headers = { "Content-Type" => "application/json" }
conn.request :google_authorization, @credentials unless @credentials.is_a? ::Symbol
conn.request :google_authorization, self.credentials unless self.credentials.is_a? ::Symbol
conn.request :retry
conn.response :raise_error
conn.adapter :net_http
Expand Down
82 changes: 82 additions & 0 deletions gapic-common/lib/gapic/universe_domain_concerns.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Gapic
##
# A mixin module that provides methods for obtaining the effective universe
# domain, endpoint, and credentials for a stub. This is included in
# Grpc::ServiceStub and Rest::ClientStub.
#
module UniverseDomainConcerns
##
# A substitution string for the universe domain in an endpoint template
# @return [String]
#
ENDPOINT_SUBSTITUTION = "$UNIVERSE_DOMAIN$".freeze

##
# The effective endpoint
# @return [String]
#
attr_reader :endpoint

##
# The effective universe domain
# @return [String]
#
attr_reader :universe_domain

##
# The effective credentials
#
# @return [Google::Auth::Credentials, Signet::OAuth2::Client, Proc,
# ::GRPC::Core::Channel, ::GRPC::Core::ChannelCredentials]
#
attr_reader :credentials

protected

##
# @private
# Called from the stub constructor to populate the data.
#
def setup_universe_domain universe_domain: nil,
endpoint: nil,
endpoint_template: nil,
credentials: nil
raise ArgumentError, "endpoint or endpoint_template is required" if endpoint.nil? && endpoint_template.nil?
raise ArgumentError, "credentials is required" if credentials.nil?

# TODO: The env logic should live in google-cloud-env
universe_domain ||= ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] || "googleapis.com"
endpoint ||= endpoint_template.sub ENDPOINT_SUBSTITUTION, universe_domain

if credentials.respond_to?(:universe_domain) && credentials.universe_domain != universe_domain
raise UniverseDomainMismatch,
"Universe domain is #{universe_domain} but credentials are in #{credentials.universe_domain}"
end

@universe_domain = universe_domain
@endpoint = endpoint
@credentials = credentials
end
end

##
# Raised when the configured universe domain does not match the universe
# domain of the credentials.
#
class UniverseDomainMismatch < ::Gapic::Common::Error
end
end
68 changes: 66 additions & 2 deletions gapic-common/test/gapic/grpc/service_stub_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ def compose call_creds
end

FakeCredentials = Class.new Google::Auth::Credentials do
def initialize
def initialize universe_domain: nil
@custom_universe_domain = universe_domain || "googleapis.com"
end

def updater_proc
->{}
end

def universe_domain
@custom_universe_domain
end
end

FakeRpcCall = Class.new do
Expand Down Expand Up @@ -216,4 +221,63 @@ def test_call_rpc_without_channel_pool

assert_equal rpc_count, 1
end
end

class FakeGrpcStub
def initialize *args, **kwargs
end
end

def test_default_universe_domain
creds = FakeCredentials.new
service_stub = Gapic::ServiceStub.new FakeGrpcStub,
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: creds
assert_equal "googleapis.com", service_stub.universe_domain
assert_equal "myservice.googleapis.com", service_stub.endpoint
end

def test_custom_universe_domain
creds = FakeCredentials.new universe_domain: "myuniverse.com"
service_stub = Gapic::ServiceStub.new FakeGrpcStub,
universe_domain: "myuniverse.com",
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: creds
assert_equal "myuniverse.com", service_stub.universe_domain
assert_equal "myservice.myuniverse.com", service_stub.endpoint
end

def test_universe_domain_env
old_domain = ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"]
ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] = "myuniverse.com"
begin
creds = FakeCredentials.new universe_domain: "myuniverse.com"
service_stub = Gapic::ServiceStub.new FakeGrpcStub,
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: creds
assert_equal "myuniverse.com", service_stub.universe_domain
assert_equal "myservice.myuniverse.com", service_stub.endpoint
ensure
ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] = old_domain
end
end

def test_universe_domain_credentials_mismatch
creds = FakeCredentials.new universe_domain: "myuniverse.com"
assert_raises Gapic::UniverseDomainMismatch do
Gapic::ServiceStub.new FakeGrpcStub,
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: creds
end
end

def test_endpoint_override
creds = FakeCredentials.new universe_domain: "myuniverse.com"
service_stub = Gapic::ServiceStub.new FakeGrpcStub,
universe_domain: "myuniverse.com",
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
endpoint: "myservice.otheruniverse.com",
credentials: creds
assert_equal "myuniverse.com", service_stub.universe_domain
assert_equal "myservice.otheruniverse.com", service_stub.endpoint
end
end
4 changes: 4 additions & 0 deletions gapic-common/test/gapic/grpc/stub_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def initialize
def updater_proc
->{}
end

def universe_domain
"googleapis.com"
end
end

def test_with_channel
Expand Down
59 changes: 59 additions & 0 deletions gapic-common/test/gapic/rest/client_stub_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,63 @@ def test_make_put_request_simple
client_stub.make_put_request uri: "/foo", body: "hello"
mock.verify
end

def test_default_universe_domain
client_stub = ::Gapic::Rest::ClientStub.new endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: :dummy_credentials
assert_equal "googleapis.com", client_stub.universe_domain
assert_equal "myservice.googleapis.com", client_stub.endpoint
end

def test_custom_universe_domain
client_stub = ::Gapic::Rest::ClientStub.new universe_domain: "myuniverse.com",
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: :dummy_credentials
assert_equal "myuniverse.com", client_stub.universe_domain
assert_equal "myservice.myuniverse.com", client_stub.endpoint
end

def test_universe_domain_env
old_domain = ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"]
ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] = "myuniverse.com"
begin
client_stub = ::Gapic::Rest::ClientStub.new endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: :dummy_credentials
assert_equal "myuniverse.com", client_stub.universe_domain
assert_equal "myservice.myuniverse.com", client_stub.endpoint
ensure
ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] = old_domain
end
end

def test_endpoint_override
client_stub = ::Gapic::Rest::ClientStub.new universe_domain: "myuniverse.com",
endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
endpoint: "myservice.otheruniverse.com",
credentials: :dummy_credentials
assert_equal "myuniverse.com", client_stub.universe_domain
assert_equal "myservice.otheruniverse.com", client_stub.endpoint
end

FakeCredentials = Class.new Google::Auth::Credentials do
def initialize universe_domain: nil
@custom_universe_domain = universe_domain || "googleapis.com"
end

def updater_proc
->{}
end

def universe_domain
@custom_universe_domain
end
end

def test_universe_domain_credentials_mismatch
creds = FakeCredentials.new universe_domain: "myuniverse.com"
assert_raises Gapic::UniverseDomainMismatch do
::Gapic::Rest::ClientStub.new endpoint_template: "myservice.$UNIVERSE_DOMAIN$",
credentials: creds
end
end
end

0 comments on commit c07eed8

Please sign in to comment.