diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb
index 32f12f7db02b8d32fecc19f7dd190cfc4c048587..003c75349cd38c66619220362292e6a4eb50bc43 100644
--- a/ee/app/policies/ee/project_policy.rb
+++ b/ee/app/policies/ee/project_policy.rb
@@ -149,6 +149,11 @@ module ProjectPolicy
         @subject.feature_available?(:combined_project_analytics_dashboards, @user)
       end
 
+      condition(:google_cloud_support_available, scope: :global) do
+        # TODO: This will be renamed to google_cloud_platform_support (https://gitlab.com/gitlab-org/gitlab/-/issues/438989)
+        ::Gitlab::Saas.feature_available?(:google_artifact_registry)
+      end
+
       condition(:status_page_available) do
         @subject.feature_available?(:status_page, @user)
       end
@@ -772,6 +777,8 @@ module ProjectPolicy
       rule { status_page_available & can?(:owner_access) }.enable :mark_issue_for_publication
       rule { status_page_available & can?(:developer_access) }.enable :publish_status_page
 
+      rule { google_cloud_support_available & can?(:maintainer_access) }.enable :read_runner_cloud_provisioning_options
+
       rule { hidden }.policy do
         prevent :download_code
         prevent :build_download_code
diff --git a/ee/app/services/google_cloud_platform/compute/base_service.rb b/ee/app/services/google_cloud_platform/compute/base_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60c9647d6f6b9d2d5f3597ef66355acebfbd888c
--- /dev/null
+++ b/ee/app/services/google_cloud_platform/compute/base_service.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+module GoogleCloudPlatform
+  module Compute
+    class BaseService < ::BaseProjectService
+      include BaseServiceUtility
+
+      VALID_ORDER_BY_COLUMNS = %w[creationTimestamp name].freeze
+      VALID_ORDER_BY_DIRECTIONS = %w[asc desc].freeze
+
+      MAX_RESULTS_LIMIT = 500
+
+      ERROR_RESPONSES = {
+        saas_only: ServiceResponse.error(message: "This is a SaaS-only feature that can't run here"),
+        feature_flag_disabled: ServiceResponse.error(message: 'Feature flag not enabled'),
+        access_denied: ServiceResponse.error(message: 'Access denied'),
+        no_integration: ServiceResponse.error(message: 'Project Artifact Registry integration not set'),
+        integration_not_active: ServiceResponse.error(message: 'Project Artifact Registry integration not active'),
+        google_cloud_authentication_error:
+          ServiceResponse.error(message: 'Unable to authenticate against Google Cloud'),
+        invalid_order_by: ServiceResponse.error(message: 'Invalid order_by value'),
+        max_results_out_of_bounds: ServiceResponse.error(message: 'Max results argument is out-of-bounds')
+      }.freeze
+
+      GCP_API_ERROR_MESSAGE = 'Unsuccessful Google Cloud API request'
+
+      def execute
+        params[:max_results] ||= MAX_RESULTS_LIMIT
+
+        validation_response = validate_before_execute
+        return validation_response if validation_response&.error?
+
+        handling_client_errors { call_client }
+      end
+
+      private
+
+      def validate_before_execute
+        return ERROR_RESPONSES[:saas_only] unless Gitlab::Saas.feature_available?(:google_artifact_registry)
+        return ERROR_RESPONSES[:feature_flag_disabled] unless Feature.enabled?(:gcp_runner, project, type: :wip)
+        return ERROR_RESPONSES[:access_denied] unless allowed?
+
+        return ERROR_RESPONSES[:no_integration] unless project_integration
+        return ERROR_RESPONSES[:integration_not_active] unless project_integration.active
+
+        return ERROR_RESPONSES[:max_results_out_of_bounds] unless (1..MAX_RESULTS_LIMIT).cover?(max_results)
+        return ERROR_RESPONSES[:invalid_order_by] unless valid_order_by?(order_by)
+
+        ServiceResponse.success
+      end
+
+      def allowed?
+        can?(current_user, :read_runner_cloud_provisioning_options, project)
+      end
+
+      def valid_order_by?(value)
+        return true if value.blank?
+
+        column, direction = value.split(' ')
+
+        return false unless column.in?(VALID_ORDER_BY_COLUMNS)
+        return false unless direction.in?(VALID_ORDER_BY_DIRECTIONS)
+
+        true
+      end
+
+      def client
+        ::GoogleCloudPlatform::Compute::Client.new(
+          project: project,
+          user: current_user,
+          gcp_project_id: gcp_project_id,
+          gcp_wlif: gcp_wlif
+        )
+      end
+
+      def gcp_project_id
+        project_integration.artifact_registry_project_id
+      end
+
+      def gcp_wlif
+        project_integration.wlif
+      end
+
+      def project_integration
+        project.google_cloud_platform_artifact_registry_integration
+      end
+      strong_memoize_attr :project_integration
+
+      def max_results
+        params[:max_results]
+      end
+
+      def filter
+        params[:filter]
+      end
+
+      def order_by
+        params[:order_by]
+      end
+
+      def page_token
+        params[:page_token]
+      end
+
+      def handling_client_errors
+        yield
+      rescue ::GoogleCloudPlatform::AuthenticationError => e
+        log_error_with_project_id(message: e.message)
+        ERROR_RESPONSES[:google_cloud_authentication_error]
+      rescue ::GoogleCloudPlatform::ApiError => e
+        log_error_with_project_id(message: e.message)
+        ServiceResponse.error(message: "#{GCP_API_ERROR_MESSAGE}: #{e.message}")
+      end
+
+      def log_error_with_project_id(message:)
+        log_error(class_name: self.class.name, project_id: project.id, message: message)
+      end
+    end
+  end
+end
diff --git a/ee/app/services/google_cloud_platform/compute/list_machine_types_service.rb b/ee/app/services/google_cloud_platform/compute/list_machine_types_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..36ac06f50b86abb5963539de06e210c36ace272e
--- /dev/null
+++ b/ee/app/services/google_cloud_platform/compute/list_machine_types_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module GoogleCloudPlatform
+  module Compute
+    class ListMachineTypesService < ::GoogleCloudPlatform::Compute::BaseService
+      MISSING_ZONE_ERROR_RESPONSE = ServiceResponse.error(message: 'Zone value must be provided').freeze
+
+      def initialize(project:, current_user:, zone:, params: {})
+        super(project: project, current_user: current_user, params: params.merge(zone: zone))
+      end
+
+      private
+
+      def zone
+        params[:zone]
+      end
+
+      def call_client
+        return MISSING_ZONE_ERROR_RESPONSE if zone.blank?
+
+        machine_types = client.machine_types(
+          zone: zone,
+          filter: filter,
+          max_results: max_results,
+          page_token: page_token,
+          order_by: order_by
+        )
+
+        ServiceResponse.success(payload: {
+          items: machine_types.items.map { |t| { name: t.name, description: t.description, zone: t.zone } },
+          next_page_token: machine_types.next_page_token
+        })
+      end
+    end
+  end
+end
diff --git a/ee/app/services/google_cloud_platform/compute/list_regions_service.rb b/ee/app/services/google_cloud_platform/compute/list_regions_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..30b29acd5b4494b1b9fdf03d29f2bdbd65ad8179
--- /dev/null
+++ b/ee/app/services/google_cloud_platform/compute/list_regions_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module GoogleCloudPlatform
+  module Compute
+    class ListRegionsService < ::GoogleCloudPlatform::Compute::BaseService
+      private
+
+      def call_client
+        regions = client.regions(filter: filter, max_results: max_results, page_token: page_token, order_by: order_by)
+        ServiceResponse.success(payload: {
+          items: regions.items.map { |r| { name: r.name, description: r.description } },
+          next_page_token: regions.next_page_token
+        })
+      end
+    end
+  end
+end
diff --git a/ee/app/services/google_cloud_platform/compute/list_zones_service.rb b/ee/app/services/google_cloud_platform/compute/list_zones_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3934c3aa73e685e0c3e629f357ae494b80fd89b
--- /dev/null
+++ b/ee/app/services/google_cloud_platform/compute/list_zones_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module GoogleCloudPlatform
+  module Compute
+    class ListZonesService < ::GoogleCloudPlatform::Compute::BaseService
+      private
+
+      def call_client
+        zones = client.zones(filter: filter, max_results: max_results, page_token: page_token, order_by: order_by)
+        ServiceResponse.success(payload: {
+          items: zones.items.map { |z| { name: z.name, description: z.description } },
+          next_page_token: zones.next_page_token
+        })
+      end
+    end
+  end
+end
diff --git a/ee/lib/google_cloud_platform/compute/client.rb b/ee/lib/google_cloud_platform/compute/client.rb
index 55e6935b7057cc25ca03bde58c733d568ec42c5f..be9734840fcf40e978631108bb0caad2d33bc58c 100644
--- a/ee/lib/google_cloud_platform/compute/client.rb
+++ b/ee/lib/google_cloud_platform/compute/client.rb
@@ -32,8 +32,8 @@ class Client < ::GoogleCloudPlatform::BaseClient
       #
       # Possible exceptions:
       #
-      # +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
-      # +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
+      # +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the authentication.
+      # +GoogleCloudPlatform::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(
@@ -71,8 +71,8 @@ def regions(filter: nil, max_results: 500, order_by: nil, page_token: nil)
       #
       # Possible exceptions:
       #
-      # +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
-      # +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
+      # +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the authentication.
+      # +GoogleCloudPlatform::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(
@@ -111,8 +111,8 @@ def zones(filter: nil, max_results: 500, order_by: nil, page_token: nil)
       #
       # Possible exceptions:
       #
-      # +GoogleCloudPlatform::Compute::BaseClient::AuthenticationError+ if an error occurs during the authentication.
-      # +GoogleCloudPlatform::Compute::BaseClient::ApiError+ if an error occurs when interacting with the
+      # +GoogleCloudPlatform::AuthenticationError+ if an error occurs during the authentication.
+      # +GoogleCloudPlatform::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(
diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb
index 05507381b362fe02874cdf2880316798b4e9d5be..a7735db0574c8026932ff4117742d38520304ad5 100644
--- a/ee/spec/policies/project_policy_spec.rb
+++ b/ee/spec/policies/project_policy_spec.rb
@@ -3531,4 +3531,28 @@ def create_member_role(member, abilities = member_role_abilities)
       end
     end
   end
+
+  describe 'read_runner_cloud_provisioning_options policy' do
+    let(:current_user) { maintainer }
+
+    it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_options) }
+
+    context 'when SaaS-only feature is available' do
+      before do
+        stub_saas_features(google_artifact_registry: true)
+      end
+
+      context 'the user is a maintainer' do
+        let(:current_user) { maintainer }
+
+        it { is_expected.to be_allowed(:read_runner_cloud_provisioning_options) }
+      end
+
+      context 'the user is a guest' do
+        let(:current_user) { guest }
+
+        it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_options) }
+      end
+    end
+  end
 end
diff --git a/ee/spec/services/google_cloud_platform/compute/list_machine_types_service_spec.rb b/ee/spec/services/google_cloud_platform/compute/list_machine_types_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4e3902246add347b0a6f06661a9ebdf9daa1970c
--- /dev/null
+++ b/ee/spec/services/google_cloud_platform/compute/list_machine_types_service_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GoogleCloudPlatform::Compute::ListMachineTypesService, feature_category: :fleet_visibility do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'for a compute service'
+
+  describe '#execute' do
+    let(:zone) { 'us-central-1a' }
+    let(:filter) { 'name=test' }
+    let(:max_results) { 50 }
+    let(:page_token) { 'token' }
+    let(:order_by) { 'name asc' }
+    let(:service) { described_class.new(project: project, current_user: user, zone: zone, params: params) }
+    let(:params) do
+      { filter: filter, max_results: max_results, page_token: page_token, order_by: order_by }
+    end
+
+    subject(:response) { service.execute }
+
+    it_behaves_like 'a compute service handling validation errors', client_method: :machine_types
+
+    context 'with saas only feature enabled' do
+      before do
+        stub_saas_features(google_artifact_registry: true)
+
+        allow(client_double).to receive(:machine_types)
+          .with(zone: zone, filter: filter, max_results: max_results, page_token: page_token, order_by: order_by)
+          .and_return(dummy_list_response)
+      end
+
+      it 'returns the machine_types' do
+        expect(response).to be_success
+        expect(response.payload[:items]).to be_a Enumerable
+        expect(response.payload[:items]).to contain_exactly({
+          name: 'test', zone: 'us-central1-a', description: 'Large machine type'
+        })
+        expect(response.payload[:next_page_token]).to eq('next_page_token')
+      end
+
+      context 'with a missing zone value' do
+        let(:zone) { nil }
+
+        it 'returns error' do
+          expect(response).to be_error
+          expect(response.message).to eq('Zone value must be provided')
+        end
+      end
+
+      context 'with an invalid order_by' do
+        where(:field, :direction) do
+          'test' | 'asc'
+          'name' | 'greater_than'
+          ''     | 'desc'
+          'name' | ''
+        end
+
+        with_them do
+          let(:order_by) { "#{field} #{direction}" }
+
+          it_behaves_like 'returning an error service response', message: 'Invalid order_by value'
+        end
+      end
+
+      context 'with an invalid max_results' do
+        where(:max_results) { [0, described_class::MAX_RESULTS_LIMIT + 1] }
+
+        with_them do
+          it_behaves_like 'returning an error service response', message: 'Max results argument is out-of-bounds'
+        end
+      end
+    end
+
+    private
+
+    def dummy_list_response
+      ::Google::Cloud::Compute::V1::MachineTypeList.new(
+        items: [
+          ::Google::Cloud::Compute::V1::MachineType.new(
+            name: 'test', zone: 'us-central1-a', description: 'Large machine type'
+          )
+        ],
+        next_page_token: 'next_page_token'
+      )
+    end
+  end
+end
diff --git a/ee/spec/services/google_cloud_platform/compute/list_regions_service_spec.rb b/ee/spec/services/google_cloud_platform/compute/list_regions_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c6d6b306416ce92cfa4c25093875931bff93257b
--- /dev/null
+++ b/ee/spec/services/google_cloud_platform/compute/list_regions_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GoogleCloudPlatform::Compute::ListRegionsService, feature_category: :fleet_visibility do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'for a compute service'
+
+  describe '#execute' do
+    let(:filter) { 'name=test' }
+    let(:max_results) { 50 }
+    let(:page_token) { 'token' }
+    let(:order_by) { 'name asc' }
+    let(:params) do
+      { filter: filter, max_results: max_results, page_token: page_token, order_by: order_by }
+    end
+
+    subject(:response) { service.execute }
+
+    it_behaves_like 'a compute service handling validation errors', client_method: :regions
+
+    context 'with saas only feature enabled' do
+      before do
+        stub_saas_features(google_artifact_registry: true)
+
+        allow(client_double).to receive(:regions)
+          .with(filter: filter, max_results: max_results, page_token: page_token, order_by: order_by)
+          .and_return(dummy_list_response)
+      end
+
+      it 'returns the regions' do
+        expect(response).to be_success
+        expect(response.payload[:items]).to be_a Enumerable
+        expect(response.payload[:items]).to contain_exactly({ name: 'test', description: 'us-central1' })
+        expect(response.payload[:next_page_token]).to eq('next_page_token')
+      end
+
+      context 'with an invalid order_by' do
+        where(:field, :direction) do
+          'test' | 'asc'
+          'name' | 'greater_than'
+          ''     | 'desc'
+          'name' | ''
+        end
+
+        with_them do
+          let(:order_by) { "#{field} #{direction}" }
+
+          it_behaves_like 'returning an error service response', message: 'Invalid order_by value'
+        end
+
+        context 'with an invalid max_results' do
+          where(:max_results) { [0, described_class::MAX_RESULTS_LIMIT + 1] }
+
+          with_them do
+            it_behaves_like 'returning an error service response', message: 'Max results argument is out-of-bounds'
+          end
+        end
+      end
+    end
+
+    private
+
+    def dummy_list_response
+      ::Google::Cloud::Compute::V1::RegionList.new(
+        items: [::Google::Cloud::Compute::V1::Region.new(name: 'test', description: 'us-central1')],
+        next_page_token: 'next_page_token'
+      )
+    end
+  end
+end
diff --git a/ee/spec/services/google_cloud_platform/compute/list_zones_service_spec.rb b/ee/spec/services/google_cloud_platform/compute/list_zones_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0a33ffbf812a4376e3c93492fc8a4b5bc78cd30c
--- /dev/null
+++ b/ee/spec/services/google_cloud_platform/compute/list_zones_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GoogleCloudPlatform::Compute::ListZonesService, feature_category: :fleet_visibility do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'for a compute service'
+
+  describe '#execute' do
+    let(:filter) { 'name=test' }
+    let(:max_results) { 50 }
+    let(:page_token) { 'token' }
+    let(:order_by) { 'name asc' }
+    let(:params) do
+      { filter: filter, max_results: max_results, page_token: page_token, order_by: order_by }
+    end
+
+    subject(:response) { service.execute }
+
+    it_behaves_like 'a compute service handling validation errors', client_method: :zones
+
+    context 'with saas only feature enabled' do
+      before do
+        stub_saas_features(google_artifact_registry: true)
+
+        allow(client_double).to receive(:zones)
+          .with(filter: filter, max_results: max_results, page_token: page_token, order_by: order_by)
+          .and_return(dummy_list_response)
+      end
+
+      it 'returns the zones' do
+        expect(response).to be_success
+        expect(response.payload[:items]).to be_a Enumerable
+        expect(response.payload[:items]).to contain_exactly({ name: 'test', description: 'us-central1-a' })
+        expect(response.payload[:next_page_token]).to eq('next_page_token')
+      end
+
+      context 'with an invalid order_by' do
+        where(:field, :direction) do
+          'test' | 'asc'
+          'name' | 'greater_than'
+          ''     | 'desc'
+          'name' | ''
+        end
+
+        with_them do
+          let(:order_by) { "#{field} #{direction}" }
+
+          it_behaves_like 'returning an error service response', message: 'Invalid order_by value'
+        end
+      end
+
+      context 'with an invalid max_results' do
+        where(:max_results) { [0, described_class::MAX_RESULTS_LIMIT + 1] }
+
+        with_them do
+          it_behaves_like 'returning an error service response', message: 'Max results argument is out-of-bounds'
+        end
+      end
+    end
+
+    private
+
+    def dummy_list_response
+      ::Google::Cloud::Compute::V1::ZoneList.new(
+        items: [::Google::Cloud::Compute::V1::Zone.new(name: 'test', description: 'us-central1-a')],
+        next_page_token: 'next_page_token'
+      )
+    end
+  end
+end
diff --git a/ee/spec/support/shared_contexts/google_cloud_platform/compute/services_shared_contexts.rb b/ee/spec/support/shared_contexts/google_cloud_platform/compute/services_shared_contexts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7eb5aece3a916f0024dc8a1ea2cbe10f7447ddb4
--- /dev/null
+++ b/ee/spec/support/shared_contexts/google_cloud_platform/compute/services_shared_contexts.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'for a compute service' do
+  let_it_be_with_reload(:project) { create(:project, :private) }
+  let_it_be_with_refind(:project_integration) do
+    create(
+      :google_cloud_platform_artifact_registry_integration,
+      project: project,
+      artifact_registry_project_id: 'gcp_project_id',
+      workload_identity_pool_project_number: '555',
+      workload_identity_pool_id: 'my_pool',
+      workload_identity_pool_provider_id: 'my_provider'
+    )
+  end
+
+  let(:user) { project.owner }
+  let(:service) { described_class.new(project: project, current_user: user, params: params) }
+  let(:client_double) { instance_double('::GoogleCloudPlatform::Compute::Client') }
+
+  before do
+    allow(::GoogleCloudPlatform::Compute::Client).to receive(:new)
+      .with(
+        project: project,
+        user: user,
+        gcp_project_id: project_integration.artifact_registry_project_id,
+        gcp_wlif: project_integration.wlif
+      ).and_return(client_double)
+  end
+end
diff --git a/ee/spec/support/shared_examples/google_cloud_platform/compute/services_shared_examples.rb b/ee/spec/support/shared_examples/google_cloud_platform/compute/services_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e9a9f56d4f68960a16f687222f2ef4c9c7112a7
--- /dev/null
+++ b/ee/spec/support/shared_examples/google_cloud_platform/compute/services_shared_examples.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a compute service handling validation errors' do |client_method:|
+  it_behaves_like 'returning an error service response', message: "This is a SaaS-only feature that can't run here"
+
+  context 'with saas only feature enabled' do
+    before do
+      stub_saas_features(google_artifact_registry: true)
+    end
+
+    shared_examples 'logging an error' do |message:|
+      it 'logs an error' do
+        expect(service).to receive(:log_error)
+          .with(class_name: described_class.name, project_id: project.id, message: message)
+
+        subject
+      end
+    end
+
+    context 'with not enough permissions' do
+      let_it_be(:user) { create(:user).tap { |user| project.add_developer(user) } }
+
+      it_behaves_like 'returning an error service response', message: 'Access denied'
+    end
+
+    context 'with gcp_runner FF disabled' do
+      before do
+        stub_feature_flags(gcp_runner: false)
+      end
+
+      it_behaves_like 'returning an error service response', message: 'Feature flag not enabled'
+    end
+
+    context 'with no integration' do
+      before do
+        project_integration.destroy!
+      end
+
+      it_behaves_like 'returning an error service response', message: 'Project Artifact Registry integration not set'
+    end
+
+    context 'with disabled integration' do
+      before do
+        project_integration.update!(active: false)
+      end
+
+      it_behaves_like 'returning an error service response', message: 'Project Artifact Registry integration not active'
+    end
+
+    context 'when client raises AuthenticationError' do
+      before do
+        allow(client_double).to receive(client_method).and_raise(::GoogleCloudPlatform::AuthenticationError, 'boom')
+      end
+
+      it_behaves_like 'returning an error service response', message: 'Unable to authenticate against Google Cloud'
+      it_behaves_like 'logging an error', message: 'boom'
+    end
+
+    context 'when client raises ApiError' do
+      before do
+        allow(client_double).to receive(client_method).and_raise(::GoogleCloudPlatform::ApiError, 'invalid arg')
+      end
+
+      it_behaves_like 'returning an error service response',
+        message: "#{described_class::GCP_API_ERROR_MESSAGE}: invalid arg"
+      it_behaves_like 'logging an error', message: 'invalid arg'
+    end
+  end
+end