Skip to content
代码片段 群组 项目
未验证 提交 72f669eb 编辑于 作者: Pedro Pombeiro's avatar Pedro Pombeiro 提交者: GitLab
浏览文件

Merge branch 'pedropombeiro/439569/refactor-client-classes' into 'master'

No related branches found
No related tags found
无相关合并请求
......@@ -14,11 +14,7 @@ def initialize(build)
def execute
return [] unless @integration&.active
config_json = ::GoogleCloudPlatform::BaseClient.credentials(
audience: @integration.wlif,
encoded_jwt: encoded_jwt
).to_json
config_json = ::GoogleCloudPlatform.credentials(audience: @integration.wlif, encoded_jwt: encoded_jwt).to_json
var_attributes = { value: config_json, public: false, masked: true, file: true }
[
......@@ -30,7 +26,7 @@ def execute
private
def encoded_jwt
JwtV2.for_build(@build, aud: ::GoogleCloudPlatform::BaseClient::GLGO_BASE_URL, wlif: @integration.wlif)
JwtV2.for_build(@build, aud: ::GoogleCloudPlatform::GLGO_BASE_URL, wlif: @integration.wlif)
end
end
end
......
# frozen_string_literal: true
module GoogleCloudPlatform
GLGO_BASE_URL = if Gitlab.staging?
'https://glgo.staging.runway.gitlab.net'
else
'https://auth.gcp.gitlab.com'
end
GLGO_TOKEN_ENDPOINT_URL = "#{GLGO_BASE_URL}/token".freeze
CREDENTIALS_TYPE = 'external_account'
STS_URL = 'https://sts.googleapis.com/v1/token'
SUBJECT_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:jwt'
CREDENTIAL_SOURCE_FORMAT = {
'type' => 'json',
'subject_token_field_name' => 'token'
}.freeze
ApiError = Class.new(StandardError)
AuthenticationError = Class.new(StandardError)
def self.credentials(audience:, encoded_jwt:)
{
type: CREDENTIALS_TYPE,
audience: audience,
token_url: STS_URL,
subject_token_type: SUBJECT_TOKEN_TYPE,
credential_source: {
url: GLGO_TOKEN_ENDPOINT_URL,
headers: { 'Authorization' => "Bearer #{encoded_jwt}" },
format: CREDENTIAL_SOURCE_FORMAT
}
}
end
end
......@@ -9,18 +9,9 @@ class Client < ::GoogleCloudPlatform::BaseClient
DEFAULT_PAGE_SIZE = 10
GCP_SUBJECT_TOKEN_ERROR_MESSAGE = 'Unable to retrieve Identity Pool subject token'
GCP_TOKEN_EXCHANGE_ERROR_MESSAGE = 'Token exchange failed'
AuthenticationError = Class.new(StandardError)
ApiError = Class.new(StandardError)
BLANK_PARAMETERS_ERROR_MESSAGE = 'All GCP parameters are required'
SAAS_ONLY_ERROR_MESSAGE = "This is a saas only feature that can't run here"
# Initialize and build a new ArtifactRegistry client.
# This will use glgo and a workload identity federation instance to exchange
# a JWT from GitLab for an access token to be used with the GCP API.
# a JWT from GitLab for an access token to be used with the Google Cloud API.
#
# +project+ The Project instance.
# +user+ The User instance.
......@@ -38,18 +29,12 @@ class Client < ::GoogleCloudPlatform::BaseClient
# +ArgumentError+ if one or more of the parameters is blank.
# +RuntimeError+ if this is used outside the Saas instance.
def initialize(project:, user:, gcp_project_id:, gcp_location:, gcp_repository:, gcp_wlif:)
raise SAAS_ONLY_ERROR_MESSAGE unless Gitlab::Saas.feature_available?(:google_artifact_registry)
super(project: project, user: user, gcp_project_id: gcp_project_id, gcp_wlif: gcp_wlif)
super(project: project, user: user)
if gcp_project_id.blank? || gcp_location.blank? || gcp_repository.blank? || gcp_wlif.blank?
raise ArgumentError, BLANK_PARAMETERS_ERROR_MESSAGE
end
raise ArgumentError, BLANK_PARAMETERS_ERROR_MESSAGE if gcp_location.blank? || gcp_repository.blank?
@gcp_project_id = gcp_project_id
@gcp_location = gcp_location
@gcp_repository = gcp_repository
@gcp_wlif = gcp_wlif
end
# Get the Artifact Registry repository object and return it.
......@@ -61,9 +46,10 @@ def initialize(project:, user:, gcp_project_id:, gcp_location:, gcp_repository:,
#
# Possible exceptions:
#
# +GoogleCloudPlatform::ArtifactRegistry::Client::AuthenticationError+ if an error occurs during the
# +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the
# authentication.
# +GoogleCloudPlatform::ArtifactRegistry::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def repository
request = ::Google::Cloud::ArtifactRegistry::V1::GetRepositoryRequest.new(name: repository_full_name)
......@@ -94,9 +80,10 @@ def repository
#
# Possible exceptions:
#
# +GoogleCloudPlatform::ArtifactRegistry::Client::AuthenticationError+ if an error occurs during the
# +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the
# authentication.
# +GoogleCloudPlatform::ArtifactRegistry::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def docker_images(page_size: nil, page_token: nil, order_by: nil)
page_size = DEFAULT_PAGE_SIZE if page_size.blank?
request = ::Google::Cloud::ArtifactRegistry::V1::ListDockerImagesRequest.new(
......@@ -115,15 +102,16 @@ def docker_images(page_size: nil, page_token: nil, order_by: nil)
# It will call the gRPC version of
# https://cloud.google.com/artifact-registry/docs/reference/rest/v1/projects.locations.repositories.dockerImages/get
#
# +name+ Name of the docker image as returned by the GCP API when using +docker_images+
# +name+ Name of the docker image as returned by the Google Cloud API when using +docker_images+
#
# Return an instance of +Google::Cloud::ArtifactRegistry::V1::DockerImage+.
#
# Possible exceptions:
#
# +GoogleCloudPlatform::ArtifactRegistry::Client::AuthenticationError+ if an error occurs during the
# +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the
# authentication.
# +GoogleCloudPlatform::ArtifactRegistry::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def docker_image(name:)
request = ::Google::Cloud::ArtifactRegistry::V1::GetDockerImageRequest.new(name: name)
......@@ -136,7 +124,7 @@ def docker_image(name:)
def gcp_client
::Google::Cloud::ArtifactRegistry::V1::ArtifactRegistry::Client.new do |config|
json_key_io = StringIO.new(::Gitlab::Json.dump(credentials(wlif: @gcp_wlif)))
json_key_io = StringIO.new(::Gitlab::Json.dump(credentials))
ext_credentials = Google::Auth::ExternalAccount::Credentials.make_creds(
json_key_io: json_key_io,
scope: CLOUD_PLATFORM_SCOPE
......@@ -146,20 +134,8 @@ def gcp_client
end
strong_memoize_attr :gcp_client
def handling_errors
yield
rescue RuntimeError => e
if e.message.include?(GCP_SUBJECT_TOKEN_ERROR_MESSAGE) || e.message.include?(GCP_TOKEN_EXCHANGE_ERROR_MESSAGE)
raise AuthenticationError, e.message
end
raise
rescue ::Google::Cloud::Error => e
raise ApiError, e.message
end
def repository_full_name
"projects/#{@gcp_project_id}/locations/#{@gcp_location}/repositories/#{@gcp_repository}"
"projects/#{gcp_project_id}/locations/#{@gcp_location}/repositories/#{@gcp_repository}"
end
strong_memoize_attr :repository_full_name
end
......
......@@ -2,66 +2,77 @@
module GoogleCloudPlatform
class BaseClient
GLGO_BASE_URL = if Gitlab.staging?
'https://glgo.staging.runway.gitlab.net'
else
'https://auth.gcp.gitlab.com'
end
GLGO_TOKEN_ENDPOINT_URL = "#{GLGO_BASE_URL}/token".freeze
CREDENTIALS_TYPE = 'external_account'
STS_URL = 'https://sts.googleapis.com/v1/token'
SUBJECT_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:jwt'
CREDENTIAL_SOURCE_FORMAT = {
'type' => 'json',
'subject_token_field_name' => 'token'
}.freeze
CLOUD_PLATFORM_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'
BLANK_PARAMETERS_ERROR_MESSAGE = 'Project and user parameters are required'
GCP_SUBJECT_TOKEN_ERROR_MESSAGE = 'Unable to retrieve glgo token'
GCP_TOKEN_EXCHANGE_ERROR_MESSAGE = 'Token exchange failed'
def self.credentials(audience:, encoded_jwt:)
{
type: CREDENTIALS_TYPE,
audience: audience,
token_url: STS_URL,
subject_token_type: SUBJECT_TOKEN_TYPE,
credential_source: {
url: GLGO_TOKEN_ENDPOINT_URL,
headers: { 'Authorization' => "Bearer #{encoded_jwt}" },
format: CREDENTIAL_SOURCE_FORMAT
}
}
end
SAAS_ONLY_ERROR_MESSAGE = "This is a SaaS-only feature that can't run here"
BLANK_PARAMETERS_ERROR_MESSAGE = 'All Google Cloud parameters are required'
def initialize(project:, user:)
raise ArgumentError, BLANK_PARAMETERS_ERROR_MESSAGE if project.blank? || user.blank?
# Initialize and build a new Compute client.
# This will use glgo and a workload identity federation instance to exchange
# a JWT from GitLab for an access token to be used with the Google Cloud API.
#
# +project+ The Project instance.
# +user+ The User instance.
# +gcp_project_id+ The Google project_id as a string. Example: 'my-project'.
# +gcp_wlif+ The Google workload identity federation string. Similar to a URL but without the
# protocol. Example:
# '//iam.googleapis.com/projects/555/locations/global/workloadIdentityPools/pool/providers/sandbox'.
#
# All parameters are required.
#
# Possible exceptions:
#
# +ArgumentError+ if one or more of the parameters is blank.
# +RuntimeError+ if this is used outside the SaaS instance.
def initialize(project:, user:, gcp_project_id:, gcp_wlif:)
if project.blank? || user.blank? || gcp_project_id.blank? || gcp_wlif.blank?
raise ArgumentError, BLANK_PARAMETERS_ERROR_MESSAGE
end
raise SAAS_ONLY_ERROR_MESSAGE unless Gitlab::Saas.feature_available?(:google_artifact_registry)
@project = project
@user = user
@gcp_project_id = gcp_project_id
@gcp_wlif = gcp_wlif
end
private
def credentials(wlif:)
self.class.credentials(
audience: wlif,
encoded_jwt: encoded_jwt(wlif: wlif)
attr_reader :project, :user, :gcp_project_id, :gcp_wlif
def credentials
::GoogleCloudPlatform.credentials(
audience: gcp_wlif,
encoded_jwt: encoded_jwt
)
end
def encoded_jwt(wlif:)
def encoded_jwt
jwt = ::GoogleCloudPlatform::Jwt.new(
project: @project,
user: @user,
project: project,
user: user,
claims: {
audience: GLGO_BASE_URL,
wlif: wlif
wlif: gcp_wlif
}
)
jwt.encoded
end
def handling_errors
yield
rescue RuntimeError => e
if e.message.include?(GCP_SUBJECT_TOKEN_ERROR_MESSAGE) || e.message.include?(GCP_TOKEN_EXCHANGE_ERROR_MESSAGE)
raise ::GoogleCloudPlatform::AuthenticationError, e.message
end
raise
rescue ::Google::Cloud::Error => e
raise ::GoogleCloudPlatform::ApiError, e.message
end
end
end
......@@ -9,43 +9,6 @@ class Client < ::GoogleCloudPlatform::BaseClient
COMPUTE_API_ENDPOINT = 'https://compute.googleapis.com'
GCP_SUBJECT_TOKEN_ERROR_MESSAGE = 'Unable to retrieve Identity Pool subject token'
GCP_TOKEN_EXCHANGE_ERROR_MESSAGE = 'Token exchange failed'
AuthenticationError = Class.new(StandardError)
ApiError = Class.new(StandardError)
BLANK_PARAMETERS_ERROR_MESSAGE = 'All GCP parameters are required'
SAAS_ONLY_ERROR_MESSAGE = "This is a saas only feature that can't run here"
# Initialize and build a new Compute client.
# This will use glgo and a workload identity federation instance to exchange
# a JWT from GitLab for an access token to be used with the GCP API.
#
# +project+ The Project instance.
# +user+ The User instance.
# +gcp_project_id+ The Google project_id as a string. Example: 'my-project'.
# +gcp_wlif+ The Google workload identity federation string. Similar to a URL but without the
# protocol. Example:
# '//iam.googleapis.com/projects/555/locations/global/workloadIdentityPools/pool/providers/sandbox'.
#
# All parameters are required.
#
# Possible exceptions:
#
# +ArgumentError+ if one or more of the parameters is blank.
# +RuntimeError+ if this is used outside the Saas instance.
def initialize(project:, user:, gcp_project_id:, gcp_wlif:)
raise SAAS_ONLY_ERROR_MESSAGE unless Gitlab::Saas.feature_available?(:google_artifact_registry)
super(project: project, user: user)
raise ArgumentError, BLANK_PARAMETERS_ERROR_MESSAGE if gcp_project_id.blank? || gcp_wlif.blank?
@gcp_project_id = gcp_project_id
@gcp_wlif = gcp_wlif
end
# Retrieves the list of region resources available to the specified project.
#
# It will call the REST version of https://cloud.google.com/compute/docs/reference/rest/v1/region/list.
......@@ -69,8 +32,9 @@ def initialize(project:, user:, gcp_project_id:, gcp_wlif:)
#
# Possible exceptions:
#
# +GoogleCloudPlatform::Compute::Client::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def regions(filter: nil, max_results: 500, order_by: nil, page_token: nil)
request = ::Google::Cloud::Compute::V1::ListRegionsRequest.new(
project: @gcp_project_id,
......@@ -107,8 +71,9 @@ def regions(filter: nil, max_results: 500, order_by: nil, page_token: nil)
#
# Possible exceptions:
#
# +GoogleCloudPlatform::Compute::Client::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def zones(filter: nil, max_results: 500, order_by: nil, page_token: nil)
request = ::Google::Cloud::Compute::V1::ListZonesRequest.new(
project: @gcp_project_id,
......@@ -146,8 +111,9 @@ def zones(filter: nil, max_results: 500, order_by: nil, page_token: nil)
#
# Possible exceptions:
#
# +GoogleCloudPlatform::Compute::Client::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::Client::ApiError+ if an error occurs when interacting with the GCP API.
# +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
# +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
# Google Cloud API.
def machine_types(zone:, filter: nil, max_results: 500, order_by: nil, page_token: nil)
request = ::Google::Cloud::Compute::V1::ListMachineTypesRequest.new(
project: @gcp_project_id,
......@@ -187,7 +153,7 @@ def client_for(klass)
end
def external_credentials
json_key_io = StringIO.new(::Gitlab::Json.dump(credentials(wlif: @gcp_wlif)))
json_key_io = StringIO.new(::Gitlab::Json.dump(credentials))
ext_credentials = Google::Auth::ExternalAccount::Credentials.make_creds(
json_key_io: json_key_io,
scope: CLOUD_PLATFORM_SCOPE
......@@ -195,18 +161,6 @@ def external_credentials
::Google::Cloud::Compute::V1::Instances::Credentials.new(ext_credentials)
end
strong_memoize_attr :external_credentials
def handling_errors
yield
rescue RuntimeError => e
if e.message.include?(GCP_SUBJECT_TOKEN_ERROR_MESSAGE) || e.message.include?(GCP_TOKEN_EXCHANGE_ERROR_MESSAGE)
raise AuthenticationError, e.message
end
raise
rescue ::Google::Cloud::Error => e
raise ApiError, e.message
end
end
end
end
......@@ -58,12 +58,12 @@
it_behaves_like 'transforming the error',
message: "test #{described_class::GCP_SUBJECT_TOKEN_ERROR_MESSAGE} test",
from_klass: RuntimeError,
to_klass: described_class::AuthenticationError
to_klass: ::GoogleCloudPlatform::AuthenticationError
it_behaves_like 'transforming the error',
message: "test #{described_class::GCP_TOKEN_EXCHANGE_ERROR_MESSAGE} test",
from_klass: RuntimeError,
to_klass: described_class::AuthenticationError
to_klass: ::GoogleCloudPlatform::AuthenticationError
it_behaves_like 'transforming the error',
message: "test",
......@@ -73,7 +73,7 @@
it_behaves_like 'transforming the error',
message: "test",
from_klass: ::Google::Cloud::Error,
to_klass: described_class::ApiError
to_klass: ::GoogleCloudPlatform::ApiError
end
describe 'validations' do
......@@ -212,9 +212,9 @@
end
def stub_authentication_requests
stub_request(:get, ::GoogleCloudPlatform::BaseClient::GLGO_TOKEN_ENDPOINT_URL)
stub_request(:get, ::GoogleCloudPlatform::GLGO_TOKEN_ENDPOINT_URL)
.to_return(status: 200, body: ::Gitlab::Json.dump(token: 'token'))
stub_request(:post, ::GoogleCloudPlatform::BaseClient::STS_URL)
stub_request(:post, ::GoogleCloudPlatform::STS_URL)
.to_return(status: 200, body: ::Gitlab::Json.dump(token: 'token'))
end
end
......@@ -52,12 +52,12 @@
it_behaves_like 'transforming the error',
message: "test #{described_class::GCP_SUBJECT_TOKEN_ERROR_MESSAGE} test",
from_klass: RuntimeError,
to_klass: described_class::AuthenticationError
to_klass: ::GoogleCloudPlatform::AuthenticationError
it_behaves_like 'transforming the error',
message: "test #{described_class::GCP_TOKEN_EXCHANGE_ERROR_MESSAGE} test",
from_klass: RuntimeError,
to_klass: described_class::AuthenticationError
to_klass: ::GoogleCloudPlatform::AuthenticationError
it_behaves_like 'transforming the error',
message: "test",
......@@ -67,7 +67,7 @@
it_behaves_like 'transforming the error',
message: "test",
from_klass: ::Google::Cloud::Error,
to_klass: described_class::ApiError
to_klass: ::GoogleCloudPlatform::ApiError
end
describe 'validations' do
......@@ -300,9 +300,9 @@
end
def stub_authentication_requests
stub_request(:get, ::GoogleCloudPlatform::BaseClient::GLGO_TOKEN_ENDPOINT_URL)
stub_request(:get, ::GoogleCloudPlatform::GLGO_TOKEN_ENDPOINT_URL)
.to_return(status: 200, body: ::Gitlab::Json.dump(token: 'token'))
stub_request(:post, ::GoogleCloudPlatform::BaseClient::STS_URL)
stub_request(:post, ::GoogleCloudPlatform::STS_URL)
.to_return(status: 200, body: ::Gitlab::Json.dump(token: 'token'))
end
end
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册