GraphQL: Add `group.runnerCloudProvisioning` field
What does this MR do and why?
This MR adds the runnerCloudProvisioning
field to GroupType
, similar to !145831 (merged) which introduced it at the ProjectType
level. This doesn't require a documentation review since it is autogenerated from an existing type.
It also temporarily removes the regions
/zones
/machineTypes
fields, since they currently only work at the project level due to JWT class limitations, and are not actually in use.
Patch to re-add support
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d2d6e23ba90a..3ebac12f6b6f 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9951,6 +9951,75 @@ The edge type for [`CiProjectVariable`](#ciprojectvariable).
| <a id="ciprojectvariableedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="ciprojectvariableedgenode"></a>`node` | [`CiProjectVariable`](#ciprojectvariable) | The item at the end of the edge. |
+#### `CiRunnerCloudProvisioningMachineTypeConnection`
+
+The connection type for [`CiRunnerCloudProvisioningMachineType`](#cirunnercloudprovisioningmachinetype).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningmachinetypeconnectionedges"></a>`edges` | [`[CiRunnerCloudProvisioningMachineTypeEdge]`](#cirunnercloudprovisioningmachinetypeedge) | A list of edges. |
+| <a id="cirunnercloudprovisioningmachinetypeconnectionnodes"></a>`nodes` | [`[CiRunnerCloudProvisioningMachineType]`](#cirunnercloudprovisioningmachinetype) | A list of nodes. |
+| <a id="cirunnercloudprovisioningmachinetypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `CiRunnerCloudProvisioningMachineTypeEdge`
+
+The edge type for [`CiRunnerCloudProvisioningMachineType`](#cirunnercloudprovisioningmachinetype).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningmachinetypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="cirunnercloudprovisioningmachinetypeedgenode"></a>`node` | [`CiRunnerCloudProvisioningMachineType`](#cirunnercloudprovisioningmachinetype) | The item at the end of the edge. |
+
+#### `CiRunnerCloudProvisioningRegionConnection`
+
+The connection type for [`CiRunnerCloudProvisioningRegion`](#cirunnercloudprovisioningregion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningregionconnectionedges"></a>`edges` | [`[CiRunnerCloudProvisioningRegionEdge]`](#cirunnercloudprovisioningregionedge) | A list of edges. |
+| <a id="cirunnercloudprovisioningregionconnectionnodes"></a>`nodes` | [`[CiRunnerCloudProvisioningRegion]`](#cirunnercloudprovisioningregion) | A list of nodes. |
+| <a id="cirunnercloudprovisioningregionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `CiRunnerCloudProvisioningRegionEdge`
+
+The edge type for [`CiRunnerCloudProvisioningRegion`](#cirunnercloudprovisioningregion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningregionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="cirunnercloudprovisioningregionedgenode"></a>`node` | [`CiRunnerCloudProvisioningRegion`](#cirunnercloudprovisioningregion) | The item at the end of the edge. |
+
+#### `CiRunnerCloudProvisioningZoneConnection`
+
+The connection type for [`CiRunnerCloudProvisioningZone`](#cirunnercloudprovisioningzone).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningzoneconnectionedges"></a>`edges` | [`[CiRunnerCloudProvisioningZoneEdge]`](#cirunnercloudprovisioningzoneedge) | A list of edges. |
+| <a id="cirunnercloudprovisioningzoneconnectionnodes"></a>`nodes` | [`[CiRunnerCloudProvisioningZone]`](#cirunnercloudprovisioningzone) | A list of nodes. |
+| <a id="cirunnercloudprovisioningzoneconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `CiRunnerCloudProvisioningZoneEdge`
+
+The edge type for [`CiRunnerCloudProvisioningZone`](#cirunnercloudprovisioningzone).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningzoneedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="cirunnercloudprovisioningzoneedgenode"></a>`node` | [`CiRunnerCloudProvisioningZone`](#cirunnercloudprovisioningzone) | The item at the end of the edge. |
+
#### `CiRunnerConnection`
The connection type for [`CiRunner`](#cirunner).
@@ -16523,6 +16592,29 @@ Returns [`CiRunnerStatus!`](#cirunnerstatus).
| ---- | ---- | ----------- |
| <a id="cirunnerstatuslegacymode"></a>`legacyMode` **{warning-solid}** | [`String`](#string) | **Deprecated** in GitLab 15.0. Will be removed in 17.0. |
+### `CiRunnerCloudProvisioningMachineType`
+
+Machine type used for runner cloud provisioning.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningmachinetypedescription"></a>`description` | [`String`](#string) | Description of the machine type. |
+| <a id="cirunnercloudprovisioningmachinetypename"></a>`name` | [`GoogleCloudMachineType`](#googlecloudmachinetype) | Name of the machine type. |
+| <a id="cirunnercloudprovisioningmachinetypezone"></a>`zone` | [`GoogleCloudZone`](#googlecloudzone) | Zone of the machine type. |
+
+### `CiRunnerCloudProvisioningRegion`
+
+Region used for runner cloud provisioning.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningregiondescription"></a>`description` | [`String`](#string) | Description of the region. |
+| <a id="cirunnercloudprovisioningregionname"></a>`name` | [`GoogleCloudRegion`](#googlecloudregion) | Name of the region. |
+
### `CiRunnerCloudProvisioningStep`
Step used to provision the runner to Google Cloud.
@@ -16535,6 +16627,17 @@ Step used to provision the runner to Google Cloud.
| <a id="cirunnercloudprovisioningsteplanguageidentifier"></a>`languageIdentifier` | [`String`](#string) | Identifier of the language used for the instructions field. This identifier can be any of the identifiers specified in the [list of supported languages and lexers](https://github.com/rouge-ruby/rouge/wiki/List-of-supported-languages-and-lexers). |
| <a id="cirunnercloudprovisioningsteptitle"></a>`title` | [`String`](#string) | Title of the step. |
+### `CiRunnerCloudProvisioningZone`
+
+Zone used for runner cloud provisioning.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnercloudprovisioningzonedescription"></a>`description` | [`String`](#string) | Description of the zone. |
+| <a id="cirunnercloudprovisioningzonename"></a>`name` | [`GoogleCloudZone`](#googlecloudzone) | Name of the zone. |
+
### `CiRunnerGoogleCloudProvisioning`
Information used for runner Google Cloud provisioning.
@@ -16544,9 +16647,26 @@ Information used for runner Google Cloud provisioning.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cirunnergooglecloudprovisioningprojectsetupshellscript"></a>`projectSetupShellScript` | [`String`](#string) | Instructions for setting up a Google Cloud project. |
+| <a id="cirunnergooglecloudprovisioningregions"></a>`regions` | [`CiRunnerCloudProvisioningRegionConnection`](#cirunnercloudprovisioningregionconnection) | Regions available for provisioning a runner. Only available for projects. (see [Connections](#connections)) |
#### Fields with arguments
+##### `CiRunnerGoogleCloudProvisioning.machineTypes`
+
+Machine types available for provisioning a runner. Only available for projects.
+
+Returns [`CiRunnerCloudProvisioningMachineTypeConnection`](#cirunnercloudprovisioningmachinetypeconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnergooglecloudprovisioningmachinetypeszone"></a>`zone` | [`GoogleCloudZone!`](#googlecloudzone) | Zone to retrieve machine types for. |
+
##### `CiRunnerGoogleCloudProvisioning.provisioningSteps`
Steps used to provision a runner in the cloud.
@@ -16562,6 +16682,22 @@ Returns [`[CiRunnerCloudProvisioningStep!]`](#cirunnercloudprovisioningstep).
| <a id="cirunnergooglecloudprovisioningprovisioningstepsrunnertoken"></a>`runnerToken` | [`String`](#string) | Authentication token of the runner. |
| <a id="cirunnergooglecloudprovisioningprovisioningstepszone"></a>`zone` | [`GoogleCloudZone!`](#googlecloudzone) | Name of the zone to provision the runner in. |
+##### `CiRunnerGoogleCloudProvisioning.zones`
+
+Zones available for provisioning a runner. Only available for projects.
+
+Returns [`CiRunnerCloudProvisioningZoneConnection`](#cirunnercloudprovisioningzoneconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cirunnergooglecloudprovisioningzonesregion"></a>`region` | [`GoogleCloudRegion`](#googlecloudregion) | Region to retrieve zones for. Returns all zones if not specified. |
+
### `CiRunnerManager`
#### Fields
diff --git a/ee/app/graphql/types/ci/runner_google_cloud_provisioning_type.rb b/ee/app/graphql/types/ci/runner_google_cloud_provisioning_type.rb
index 7f0ffc898365..49ae83e67ff4 100644
--- a/ee/app/graphql/types/ci/runner_google_cloud_provisioning_type.rb
+++ b/ee/app/graphql/types/ci/runner_google_cloud_provisioning_type.rb
@@ -12,6 +12,34 @@ class RunnerGoogleCloudProvisioningType < BaseObject
authorize :read_runner_cloud_provisioning_info
+ field :regions, Types::Ci::RunnerCloudProvisioningRegionType.connection_type,
+ description: 'Regions available for provisioning a runner. Only available for projects.',
+ null: true,
+ connection_extension: Gitlab::Graphql::Extensions::ForwardOnlyExternallyPaginatedArrayExtension,
+ max_page_size: GoogleCloudPlatform::Compute::ListRegionsService::MAX_RESULTS_LIMIT,
+ default_page_size: GoogleCloudPlatform::Compute::ListRegionsService::MAX_RESULTS_LIMIT
+
+ field :zones, Types::Ci::RunnerCloudProvisioningZoneType.connection_type,
+ description: 'Zones available for provisioning a runner. Only available for projects.',
+ null: true,
+ connection_extension: Gitlab::Graphql::Extensions::ForwardOnlyExternallyPaginatedArrayExtension,
+ max_page_size: GoogleCloudPlatform::Compute::ListZonesService::MAX_RESULTS_LIMIT,
+ default_page_size: GoogleCloudPlatform::Compute::ListZonesService::MAX_RESULTS_LIMIT do
+ argument :region, Types::GoogleCloud::RegionType, required: false,
+ description: 'Region to retrieve zones for. Returns all zones if not specified.'
+ end
+
+ field :machine_types,
+ Types::Ci::RunnerCloudProvisioningMachineTypeType.connection_type,
+ description: 'Machine types available for provisioning a runner. Only available for projects.',
+ null: true,
+ connection_extension: Gitlab::Graphql::Extensions::ForwardOnlyExternallyPaginatedArrayExtension,
+ max_page_size: GoogleCloudPlatform::Compute::ListMachineTypesService::MAX_RESULTS_LIMIT,
+ default_page_size: GoogleCloudPlatform::Compute::ListMachineTypesService::MAX_RESULTS_LIMIT do
+ argument :zone, Types::GoogleCloud::ZoneType, required: true,
+ description: 'Zone to retrieve machine types for.'
+ end
+
field :project_setup_shell_script, GraphQL::Types::String, null: true,
description: 'Instructions for setting up a Google Cloud project.'
@@ -23,6 +51,44 @@ def self.authorized?(object, context)
super(object[:container], context)
end
+ def regions(after: nil, first: nil)
+ check_wlif_availability!
+
+ response = GoogleCloudPlatform::Compute::ListRegionsService
+ .new(container: container, current_user: current_user,
+ params: default_params(after, first).merge(google_cloud_project_id: google_cloud_project_id))
+ .execute
+
+ externally_paginated_array(response, after)
+ end
+
+ def zones(region: nil, after: nil, first: nil)
+ check_wlif_availability!
+
+ params = default_params(after, first)
+ params[:filter] = "name=#{region}-*" if region
+ params[:google_cloud_project_id] = google_cloud_project_id if google_cloud_project_id
+
+ response = GoogleCloudPlatform::Compute::ListZonesService
+ .new(container: container, current_user: current_user, params: params)
+ .execute
+
+ externally_paginated_array(response, after)
+ end
+
+ def machine_types(zone:, after: nil, first: nil)
+ check_wlif_availability!
+
+ response = GoogleCloudPlatform::Compute::ListMachineTypesService
+ .new(
+ container: container, current_user: current_user, zone: zone,
+ params: default_params(after, first).merge(google_cloud_project_id: google_cloud_project_id)
+ )
+ .execute
+
+ externally_paginated_array(response, after)
+ end
+
def project_setup_shell_script
template = ERB.new(File.read(SHELL_SCRIPT_TEMPLATE_PATH))
@@ -42,6 +108,26 @@ def container
def google_cloud_project_id
object[:cloud_project_id]
end
+
+ def default_params(after, first)
+ { max_results: first, page_token: after }.compact
+ end
+
+ def externally_paginated_array(response, after)
+ raise_resource_not_available_error!(response.message) if response.error?
+
+ Gitlab::Graphql::ExternallyPaginatedArray.new(
+ after,
+ response.payload[:next_page_token],
+ *response.payload[:items]
+ )
+ end
+
+ def check_wlif_availability!
+ return if container.is_a?(Project)
+
+ raise_resource_not_available_error!('This field is currently only available for projects')
+ end
end
end
end
diff --git a/ee/spec/requests/api/graphql/project/runner_google_cloud_provisioning_spec.rb b/ee/spec/requests/api/graphql/project/runner_google_cloud_provisioning_spec.rb
index 9318ed14121e..1ec1eba044d2 100644
--- a/ee/spec/requests/api/graphql/project/runner_google_cloud_provisioning_spec.rb
+++ b/ee/spec/requests/api/graphql/project/runner_google_cloud_provisioning_spec.rb
@@ -7,11 +7,6 @@
using RSpec::Parameterized::TableSyntax
let_it_be_with_refind(:group) { create(:group) }
- let_it_be(:group_owner) { create(:user).tap { |user| group.add_owner(user) } }
- let_it_be_with_refind(:group_wlif_integration) do
- create(:google_cloud_platform_workload_identity_federation_integration, project: nil, group: group)
- end
-
let_it_be_with_refind(:project) { create(:project, group: group) }
let_it_be(:project_maintainer) { create(:user).tap { |user| group.add_maintainer(user) } }
let_it_be_with_refind(:project_wlif_integration) do
@@ -45,156 +40,375 @@
stub_saas_features(google_cloud_support: true)
end
- where(:parent_field, :container, :current_user) do
- :group | ref(:group) | ref(:group_owner)
- :project | ref(:project) | ref(:project_maintainer)
- end
+ describe 'collections' do
+ # the collection methods currently only work for projects, due to the fact that GoogleCloudPlatform::Jwt
+ # only accepts projects
+ let(:container) { project }
+ let(:parent_field) { :project }
+ let(:current_user) { project_maintainer }
+ let(:client_klass) { GoogleCloudPlatform::Compute::Client }
+ let(:expected_compute_client_args) do
+ {
+ wlif_integration: container.google_cloud_platform_workload_identity_federation_integration,
+ user: current_user,
+ params: { google_cloud_project_id: google_cloud_project_id }
+ }
+ end
- with_them do
- context 'when cloud_project_id is invalid' do
- let(:google_cloud_project_id) { 'project_id_override' }
+ let(:current_page_token) { nil }
+ let(:expected_next_page_token) { nil }
+ let(:base_item_query_args) { {} }
+ let(:item_query_args) { {} }
+ let(:node_name) { :regions }
+ let(:item_type) { 'CiRunnerCloudProvisioningRegion' }
+ let(:inner_fragment) do
+ query_nodes(
+ node_name,
+ args: base_item_query_args.merge(item_query_args),
+ of: item_type,
+ include_pagination_info: true)
+ end
- it 'returns an error' do
- request
+ shared_examples 'a query handling client errors' do
+ shared_examples 'returns error when client raises' do |error_klass, message|
+ it "returns error when client raises #{error_klass}" do
+ expect_next_instance_of(GoogleCloudPlatform::Compute::Client, expected_compute_client_args) do |client|
+ expect(client).to receive(client_method).and_raise(error_klass, message)
+ end
- expect_graphql_errors_to_include('"project_id_override" is not a valid project name')
+ post_graphql(query, current_user: current_user)
+ expect_graphql_errors_to_include(message)
+ end
end
+
+ it_behaves_like 'returns error when client raises', GoogleCloudPlatform::ApiError, 'api error'
+ it_behaves_like 'returns error when client raises', GoogleCloudPlatform::AuthenticationError,
+ 'Unable to authenticate against Google Cloud'
end
- describe 'projectSetupShellScript' do
- let(:inner_fragment) { 'projectSetupShellScript' }
- let(:options_response) do
+ shared_examples 'a query calling compute client' do
+ let(:page_size) { GoogleCloudPlatform::Compute::BaseService::MAX_RESULTS_LIMIT }
+ let(:actual_returned_nodes) { returned_nodes }
+ let(:expected_client_args) { {} }
+ let(:expected_pagination_client_args) do
+ { max_results: page_size, page_token: current_page_token, order_by: nil }
+ end
+
+ before do
+ allow_next_instance_of(client_klass, expected_compute_client_args) do |client|
+ allow(client).to receive(client_method)
+ .with(a_hash_including(**expected_pagination_client_args.merge(expected_client_args))) do
+ compute_type = client_method.to_s.camelize.singularize
+ google_cloud_object_list(compute_type, actual_returned_nodes, next_page_token: expected_next_page_token)
+ end
+ end
+
request
+ end
- graphql_data_at(
- GraphqlHelpers.fieldnamerize(parent_field), 'runnerCloudProvisioning', 'projectSetupShellScript')
+ shared_examples 'a client returning paginated response' do
+ it 'returns paginated response with items from client' do
+ graphql_field_name = GraphqlHelpers.fieldnamerize(client_method)
+
+ expect(options_response[graphql_field_name]).to match({
+ 'nodes' => expected_nodes.map { |node_props| a_graphql_entity_for(nil, **node_props) },
+ 'pageInfo' => a_hash_including(
+ 'hasPreviousPage' => !!current_page_token,
+ 'hasNextPage' => !!expected_next_page_token,
+ 'endCursor' => expected_next_page_token
+ )
+ })
+ end
end
- it 'returns a script' do
- request
- expect_graphql_errors_to_be_empty
+ it_behaves_like 'a working graphql query'
+ it_behaves_like 'a client returning paginated response'
+
+ context 'with arguments' do
+ let(:current_page_token) { 'prev_page_token' }
+ let(:page_size) { 10 }
+ let(:base_item_query_args) do
+ { after: current_page_token, first: page_size }
+ end
+
+ it_behaves_like 'a client returning paginated response'
+
+ context 'with pagination arguments requesting next page' do
+ let(:current_page_token) { 'next_page_token' }
+ let(:expected_next_page_token) { 'next_page_token2' }
+ let(:page_size) { 1 }
+ let(:expected_nodes) { returned_nodes[1..] }
+ let(:actual_returned_nodes) { returned_nodes[1..] }
+ let(:base_item_query_args) { { after: current_page_token, first: page_size } }
+
+ it_behaves_like 'a client returning paginated response'
+ end
+ end
+ end
+
+ describe 'regions' do
+ let(:item_type) { 'CiRunnerCloudProvisioningRegion' }
+ let(:client_method) { :regions }
+ let(:node_name) { :regions }
+ let(:regions) do
+ [
+ { name: 'us-east1', description: 'us-east1' },
+ { name: 'us-west1', description: 'us-west1' }
+ ]
+ end
+
+ let(:returned_nodes) { regions }
+ let(:expected_nodes) { returned_nodes }
+ let(:expected_client_args) { { filter: nil } }
+
+ it_behaves_like 'a query handling client errors'
+ it_behaves_like 'a query calling compute client'
+ end
+
+ describe 'zones' do
+ let(:item_type) { 'CiRunnerCloudProvisioningZone' }
+ let(:client_method) { :zones }
+ let(:node_name) { :zones }
+ let(:zones) do
+ [
+ { name: 'us-east1-a', description: 'us-east1-a' },
+ { name: 'us-west1-a', description: 'us-west1-a' }
+ ]
+ end
+
+ let(:returned_nodes) { zones }
+ let(:expected_nodes) { returned_nodes }
+ let(:expected_client_args) { { filter: nil } }
- expect(options_response).to be_a(String)
- expect(options_response).to include google_cloud_project_id
+ it_behaves_like 'a query handling client errors'
+ it_behaves_like 'a query calling compute client'
+
+ context 'with specified region' do
+ let(:region) { 'us-east1' }
+ let(:item_query_args) { { region: region } }
+ let(:returned_nodes) { zones.select { |z| z[:name].starts_with?(region) } }
+ let(:expected_next_page_token) { 'next_page_token' }
+
+ it_behaves_like 'a query calling compute client' do
+ let(:expected_client_args) { { filter: "name=#{region}-*" } }
+ end
end
end
- describe 'provisioningSteps' do
- let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
+ describe 'machineTypes' do
+ let(:item_type) { 'CiRunnerCloudProvisioningMachineType' }
+ let(:client_method) { :machine_types }
+ let(:node_name) { :machine_types }
+ let(:machine_types) do
+ [
+ { zone: zone, name: 'e2-highcpu-8', description: 'Efficient Instance, 8 vCPUs, 8 GB RAM' },
+ { zone: zone, name: 'e2-highcpu-16', description: 'Efficient Instance, 16 vCPUs, 16 GB RAM' }
+ ]
+ end
- let(:region) { 'us-central1' }
- let(:zone) { 'us-central1-a' }
- let(:machine_type) { 'n2d-standard-2' }
- let(:runner_token) { runner.token }
- let(:args) do
- {
- region: region,
- zone: zone,
- ephemeral_machine_type: machine_type,
- runner_token: runner_token
- }
+ let(:zone) { 'us-east1-a' }
+ let(:item_query_args) { { zone: zone } }
+ let(:returned_nodes) { machine_types }
+ let(:expected_nodes) { returned_nodes }
+ let(:expected_client_args) { { filter: "name=#{zone}-*" } }
+
+ it_behaves_like 'a query handling client errors'
+ it_behaves_like 'a query calling compute client'
+ end
+
+ context 'when integration is not present' do
+ before do
+ container.google_cloud_platform_workload_identity_federation_integration.destroy!
end
- let(:inner_fragment) do
- query_graphql_field(:provisioning_steps, args,
- all_graphql_fields_for('CiRunnerCloudProvisioningStep'), '[CiRunnerCloudProvisioningStep!]')
+ it 'returns error' do
+ post_graphql(query, current_user: current_user)
+ expect_graphql_errors_to_include(/integration not set/)
end
+ end
- let(:options_response) do
- request
- graphql_data_at(GraphqlHelpers.fieldnamerize(parent_field), 'runnerCloudProvisioning', 'provisioningSteps')
+ context 'when integration is inactive' do
+ before do
+ container.google_cloud_platform_workload_identity_federation_integration.update_column(:active, false)
end
- it 'returns provisioning steps', :aggregate_failures do
- request
- expect_graphql_errors_to_be_empty
+ it 'returns error' do
+ post_graphql(query, current_user: current_user)
+ expect_graphql_errors_to_include(/integration not active/)
+ end
+ end
- expect(options_response).to match([
- {
- 'instructions' => /google_project += "#{google_cloud_project_id}"/,
- 'languageIdentifier' => 'terraform',
- 'title' => 'Save the Terraform script to a file'
- },
+ private
+
+ def google_cloud_object_list(compute_type, returned_nodes, next_page_token:)
+ item_type = "Google::Cloud::Compute::V1::#{compute_type}"
+
+ # rubocop:disable RSpec/VerifiedDoubles -- these generated objects don't actually expose the methods
+ double("#{item_type}List",
+ items: returned_nodes.map { |props| double(item_type, **props) },
+ next_page_token: next_page_token
+ )
+ # rubocop:enable RSpec/VerifiedDoubles
+ end
+ end
+
+ describe 'common group/project fields' do
+ let_it_be(:group_owner) { create(:user).tap { |user| group.add_owner(user) } }
+ let_it_be_with_refind(:group_wlif_integration) do
+ create(:google_cloud_platform_workload_identity_federation_integration, project: nil, group: group)
+ end
+
+ where(:parent_field, :container, :current_user) do
+ :group | ref(:group) | ref(:group_owner)
+ :project | ref(:project) | ref(:project_maintainer)
+ end
+
+ with_them do
+ context 'when cloud_project_id is invalid' do
+ let(:google_cloud_project_id) { 'project_id_override' }
+
+ it 'returns an error' do
+ request
+
+ expect_graphql_errors_to_include('"project_id_override" is not a valid project name')
+ end
+ end
+
+ describe 'projectSetupShellScript' do
+ let(:inner_fragment) { 'projectSetupShellScript' }
+ let(:options_response) do
+ request
+
+ graphql_data_at(
+ GraphqlHelpers.fieldnamerize(parent_field), 'runnerCloudProvisioning', 'projectSetupShellScript')
+ end
+
+ it 'returns a script' do
+ request
+ expect_graphql_errors_to_be_empty
+
+ expect(options_response).to be_a(String)
+ expect(options_response).to include google_cloud_project_id
+ end
+ end
+
+ describe 'provisioningSteps' do
+ let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ let(:region) { 'us-central1' }
+ let(:zone) { 'us-central1-a' }
+ let(:machine_type) { 'n2d-standard-2' }
+ let(:runner_token) { runner.token }
+ let(:args) do
{
- 'instructions' => /gitlab_runner="#{runner_token}"/,
- 'languageIdentifier' => 'shell',
- 'title' => 'Apply the Terraform script'
+ region: region,
+ zone: zone,
+ ephemeral_machine_type: machine_type,
+ runner_token: runner_token
}
- ])
- end
+ end
- context 'with nil runner token' do
- let(:runner_token) { nil }
+ let(:inner_fragment) do
+ query_graphql_field(:provisioning_steps, args,
+ all_graphql_fields_for('CiRunnerCloudProvisioningStep'), '[CiRunnerCloudProvisioningStep!]')
+ end
+
+ let(:options_response) do
+ request
+ graphql_data_at(GraphqlHelpers.fieldnamerize(parent_field), 'runnerCloudProvisioning', 'provisioningSteps')
+ end
- it 'is successful and generates a unique deployment id' do
+ it 'returns provisioning steps', :aggregate_failures do
request
expect_graphql_errors_to_be_empty
expect(options_response).to match([
- a_hash_including('instructions' => /name = "grit-[A-Za-z0-9_\-]{8}"/),
- an_instance_of(Hash)
+ {
+ 'instructions' => /google_project += "#{google_cloud_project_id}"/,
+ 'languageIdentifier' => 'terraform',
+ 'title' => 'Save the Terraform script to a file'
+ },
+ {
+ 'instructions' => /gitlab_runner="#{runner_token}"/,
+ 'languageIdentifier' => 'shell',
+ 'title' => 'Apply the Terraform script'
+ }
])
end
- context 'when user does not have permissions to create runner' do
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(current_user, :create_runner, anything).and_return(false)
- end
+ context 'with nil runner token' do
+ let(:runner_token) { nil }
- it 'returns an error' do
+ it 'is successful and generates a unique deployment id' do
request
+ expect_graphql_errors_to_be_empty
- expect_graphql_errors_to_include(s_('Runners|The user is not allowed to create a runner'))
+ expect(options_response).to match([
+ a_hash_including('instructions' => /name = "grit-[A-Za-z0-9_\-]{8}"/),
+ an_instance_of(Hash)
+ ])
end
- end
- end
- context 'with invalid runner token' do
- let(:runner_token) { 'invalid-token' }
+ context 'when user does not have permissions to create runner' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(current_user, :create_runner, anything).and_return(false)
+ end
- it 'returns an error' do
- request
+ it 'returns an error' do
+ request
- expect_graphql_errors_to_include(s_('Runners|The runner authentication token is invalid'))
+ expect_graphql_errors_to_include(s_('Runners|The user is not allowed to create a runner'))
+ end
+ end
end
- end
- context 'when user cannot provision runners' do
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(current_user, :provision_cloud_runner, container)
- .and_return(false)
+ context 'with invalid runner token' do
+ let(:runner_token) { 'invalid-token' }
+
+ it 'returns an error' do
+ request
+
+ expect_graphql_errors_to_include(s_('Runners|The runner authentication token is invalid'))
+ end
end
- it 'returns an error' do
- request
+ context 'when user cannot provision runners' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(current_user, :provision_cloud_runner, container)
+ .and_return(false)
+ end
+
+ it 'returns an error' do
+ request
- expect_graphql_errors_to_include("You don't have permissions to provision cloud runners")
+ expect_graphql_errors_to_include("You don't have permissions to provision cloud runners")
+ end
end
end
- end
- context 'when user is not a maintainer or higher' do
- let(:current_user) { create(:user).tap { |user| container.add_developer(user) } }
+ context 'when user is not a maintainer or higher' do
+ let(:current_user) { create(:user).tap { |user| container.add_developer(user) } }
- it { is_expected.to be nil }
- end
-
- context 'when SaaS feature is not enabled' do
- before do
- stub_saas_features(google_cloud_support: false)
+ it { is_expected.to be nil }
end
- it { is_expected.to be nil }
- end
+ context 'when SaaS feature is not enabled' do
+ before do
+ stub_saas_features(google_cloud_support: false)
+ end
- context 'when google_cloud_runner_provisioning FF is disabled' do
- before do
- stub_feature_flags(google_cloud_runner_provisioning: false)
+ it { is_expected.to be nil }
end
- it { is_expected.to be nil }
+ context 'when google_cloud_runner_provisioning FF is disabled' do
+ before do
+ stub_feature_flags(google_cloud_runner_provisioning: false)
+ end
+
+ it { is_expected.to be nil }
+ end
end
end
end
EE: true
Closes #438316 (closed)
Review status
Reviews | Reviewer | Status |
---|---|---|
backend (initial) | @10io | ![]() |
backend (maintainer) | @allison.browne | ![]() |
devopsverify | @allison.browne | ![]() |
authorization | @jarka | ![]() |
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
-
Run the following query in http://gdk.test:3000/-/graphql-explorer:
{ group(fullPath: "gitlab-org") { id runnerCloudProvisioning( provider: GOOGLE_CLOUD cloudProjectId: "dev-gcp-s3c-integrati-9abafed1" ) { ... on CiRunnerGoogleCloudProvisioning { provisioningSteps( region: "us-central1" zone: "us-central1-a" ephemeralMachineType: "n2d-standard-2" ) { title languageIdentifier instructions } } } } project(fullPath: "gitlab-org/playground") { id runnerCloudProvisioning( provider: GOOGLE_CLOUD cloudProjectId: "dev-gcp-s3c-integrati-9abafed1" ) { ... on CiRunnerGoogleCloudProvisioning { zones(first: 2) { nodes { description } } provisioningSteps( region: "us-central1" zone: "us-central1-a" ephemeralMachineType: "n2d-standard-2" ) { title languageIdentifier instructions } } } } }
Merge request reports
Activity
changed milestone to %16.10
added Category:Fleet Visibility devopsverify grouprunner sectionci labels
assigned to @pedropombeiro
- A deleted user
added backend documentation labels
4 Warnings This merge request is quite big (1144 lines changed), please consider splitting it into multiple merge requests. 89e8b069: Commits that change 30 or more lines across at least 3 files should describe these changes in the commit body. For more information, take a look at our Commit message guidelines. 4d335c4f: Commits that change 30 or more lines across at least 3 files should describe these changes in the commit body. For more information, take a look at our Commit message guidelines. fcc5ba06: Commits that change 30 or more lines across at least 3 files should describe these changes in the commit body. For more information, take a look at our Commit message guidelines. 2 Messages CHANGELOG missing: If this merge request needs a changelog entry, add the
Changelog
trailer to the commit message you want to add to the changelog.If this merge request doesn't need a CHANGELOG entry, feel free to ignore this message.
This merge request adds or changes documentation files. A review from the Technical Writing team before you merge is recommended. Reviews can happen after you merge. Documentation review
The following files require a review from a technical writer:
-
doc/api/graphql/reference/index.md
(Link to current live version)
The review does not need to block merging this merge request. See the:
-
Metadata for the
*.md
files that you've changed. The first few lines of each*.md
file identify the stage and group most closely associated with your docs change. - The Technical Writer assigned for that stage and group.
- Documentation workflows for information on when to assign a merge request for review.
Reviewer roulette
Category Reviewer Maintainer backend @Quintasan
(UTC+1, same timezone as author)
@hfyngvason
(UTC-5, 6 hours behind author)
~"Authorization" Reviewer review is optional for ~"Authorization" @jarka
(UTC+1, same timezone as author)
~"Verify" Reviewer review is optional for ~"Verify" @alberts-gitlab
(UTC+8, 7 hours ahead of author)
Please check reviewer's status!
Please refer to documentation page for guidance on how you can benefit from the Reviewer Roulette, or use the GitLab Review Workload Dashboard to find other available reviewers.
If needed, you can retry the
danger-review
job that generated this comment.Generated by
Danger-
- Resolved by 🤖 GitLab Bot 🤖
Proper labels assigned to this merge request. Please ignore me.
@pedropombeiro - please see the following guidance and update this merge request.1 Error Please add typebug typefeature, or typemaintenance label to this merge request. Edited by 🤖 GitLab Bot 🤖
- A deleted user
added frontend label
added 63 commits
-
65a959d7...fd399005 - 62 commits from branch
master
- a06345e6 - GraphQL: Add `group.runnerCloudProvisioning` field
-
65a959d7...fd399005 - 62 commits from branch
removed frontend label
- Resolved by Allison Browne
requested review from @10io
added 1 commit
- 1ee03721 - GraphQL: Add `group.runnerCloudProvisioning` field
Hey @vshushlin
, mind doing the Verify review?requested review from @vshushlin
cc @grzesiek
added pipeline:mr-approved label
- Resolved by Pedro Pombeiro
@jarka
, thanks for approving this merge request.This is the first time the merge request has been approved. To ensure we don't only run predictive pipelines, and we don't break
master
, a new pipeline will be started shortly.Please wait for the pipeline to start before resolving this discussion and set auto-merge for the new pipeline. See merging a merge request for more details.
E2E Test Result Summary
allure-report-publisher
generated test report!e2e-test-on-gdk:
test report for 89e8b069expand test summary
+------------------------------------------------------------------+ | suites summary | +-------------+--------+--------+---------+-------+-------+--------+ | | passed | failed | skipped | flaky | total | result | +-------------+--------+--------+---------+-------+-------+--------+ | Verify | 35 | 0 | 1 | 0 | 36 | ✅ | | Create | 8 | 0 | 3 | 0 | 11 | ✅ | | Govern | 3 | 0 | 0 | 0 | 3 | ✅ | | Package | 0 | 0 | 1 | 0 | 1 | ➖ | | Plan | 4 | 0 | 0 | 0 | 4 | ✅ | | Monitor | 4 | 0 | 0 | 0 | 4 | ✅ | | Data Stores | 2 | 0 | 0 | 0 | 2 | ✅ | +-------------+--------+--------+---------+-------+-------+--------+ | Total | 56 | 0 | 5 | 0 | 61 | ✅ | +-------------+--------+--------+---------+-------+-------+--------+
e2e-package-and-test:
test report for 89e8b069expand test summary
+------------------------------------------------------------------+ | suites summary | +-------------+--------+--------+---------+-------+-------+--------+ | | passed | failed | skipped | flaky | total | result | +-------------+--------+--------+---------+-------+-------+--------+ | Verify | 147 | 0 | 30 | 0 | 177 | ✅ | | Create | 153 | 0 | 19 | 0 | 172 | ✅ | | Govern | 6 | 0 | 0 | 0 | 6 | ✅ | | Data Stores | 4 | 0 | 0 | 0 | 4 | ✅ | | Plan | 8 | 0 | 0 | 0 | 8 | ✅ | | Monitor | 8 | 0 | 0 | 0 | 8 | ✅ | | Package | 0 | 0 | 2 | 0 | 2 | ➖ | +-------------+--------+--------+---------+-------+-------+--------+ | Total | 326 | 0 | 51 | 0 | 377 | ✅ | +-------------+--------+--------+---------+-------+-------+--------+
- Resolved by Pedro Pombeiro
- Resolved by Pedro Pombeiro
removed review request for @10io
added 121 commits
-
1ee03721...c4bf8062 - 120 commits from branch
master
- 1aa674ee - GraphQL: Add `group.runnerCloudProvisioning` field
-
1ee03721...c4bf8062 - 120 commits from branch
reset approvals from @jarka by pushing to the branch
requested review from @10io
- Resolved by Allison Browne
- Resolved by Pedro Pombeiro
- Resolved by Pedro Pombeiro
- Resolved by Pedro Pombeiro
- Resolved by Allison Browne
Thanks, @pedropombeiro!
I left a few comments, otherwise looks great
removed review request for @vshushlin
- Resolved by Pedro Pombeiro
- Resolved by David Fernandez
- Resolved by Pedro Pombeiro
- Resolved by David Fernandez
removed review request for @10io
added 89 commits
-
4d6f67dd...21669dbd - 86 commits from branch
master
- f3b8ee56 - GraphQL: Add `group.runnerCloudProvisioning` field
- 6bdd1244 - Address MR review comments
- a8bded1e - Address MR review comments
Toggle commit list-
4d6f67dd...21669dbd - 86 commits from branch
removed pipeline:mr-approved label
requested review from @10io and @vshushlin
requested review from @jarka
removed review request for @jarka
mentioned in issue #443855 (closed)
removed review request for @10io
requested review from @10io
added 136 commits
-
832c2e08...9ec41b0a - 132 commits from branch
master
- 5224279b - GraphQL: Add `group.runnerCloudProvisioning` field
- 6ad14605 - Address MR review comments
- 707efd36 - Address MR review comments
- afb760b9 - Remove regions/zones/machine types
Toggle commit list-
832c2e08...9ec41b0a - 132 commits from branch
- Resolved by Pedro Pombeiro
removed review request for @10io
requested review from @10io
added pipeline:mr-approved label
@allison.browne can you take over the verify review please?
requested review from @allison.browne and removed review request for @vshushlin
mentioned in issue #438316 (closed)
removed review request for @10io
added 114 commits
-
e571425d...f763ca29 - 110 commits from branch
master
- abfb4ad9 - GraphQL: Add `group.runnerCloudProvisioning` field
- 92f6fbef - Address MR review comments
- ba171a76 - Address MR review comments
- 8598a5bd - Remove regions/zones/machine types
Toggle commit list-
e571425d...f763ca29 - 110 commits from branch
removed pipeline:mr-approved label
- Resolved by Pedro Pombeiro
Hey @jarka
, the latest commit removing some unused code caused your approval to be dropped. Can you please reapprove?
added pipeline:mr-approved label
mentioned in issue #444235
mentioned in merge request !146405 (merged)
added 104 commits
-
979de008...ea9d193e - 100 commits from branch
master
- 7143e0be - GraphQL: Add `group.runnerCloudProvisioning` field
- fcc5ba06 - Address MR review comments
- 4d335c4f - Address MR review comments
- 89e8b069 - Remove regions/zones/machine types
Toggle commit list-
979de008...ea9d193e - 100 commits from branch
reset approvals from @allison.browne by pushing to the branch
Hey @vshushlin
, this MR lost Allison's approval due to a merge conflict. Could you please reapprove?requested review from @vshushlin and removed review request for @allison.browne
requested review from @mbobin
enabled an automatic merge when the pipeline for 3a5ebd26 succeeds
mentioned in commit 6aad8686
added workflowstaging-canary label
added workflowcanary label and removed workflowstaging-canary label
added workflowstaging label and removed workflowcanary label
added workflowproduction label and removed workflowstaging label
added workflowpost-deploy-db-staging label and removed workflowproduction label
mentioned in issue #448400
added releasedpublished label
added pipelinetier-3 label