Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-29 21:08:53 +00:00
parent a16398e10f
commit a5c89610f3
71 changed files with 725 additions and 202 deletions

View file

@ -60,26 +60,6 @@ Layout/SpaceInsideBlockBraces:
- 'ee/spec/elastic_integration/global_search_spec.rb'
- 'ee/spec/factories/dast/profiles_pipelines.rb'
- 'ee/spec/factories/licenses.rb'
- 'ee/spec/features/billings/billing_plans_spec.rb'
- 'ee/spec/features/boards/board_filters_spec.rb'
- 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- 'ee/spec/features/boards/swimlanes/epics_swimlanes_filtering_spec.rb'
- 'ee/spec/features/epics/todo_spec.rb'
- 'ee/spec/features/google_analytics_datalayer_spec.rb'
- 'ee/spec/features/groups/issues_spec.rb'
- 'ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb'
- 'ee/spec/features/issues/form_spec.rb'
- 'ee/spec/features/issues/issue_sidebar_spec.rb'
- 'ee/spec/features/issues/user_edits_issue_spec.rb'
- 'ee/spec/features/merge_request/user_edits_multiple_reviewers_mr_spec.rb'
- 'ee/spec/features/merge_request/user_sees_closing_issues_message_spec.rb'
- 'ee/spec/features/merge_requests/user_resets_approvers_spec.rb'
- 'ee/spec/features/merge_requests/user_views_all_merge_requests_spec.rb'
- 'ee/spec/features/projects/integrations/user_activates_github_spec.rb'
- 'ee/spec/features/projects/push_rules_spec.rb'
- 'ee/spec/features/projects/security/dast_scanner_profiles_spec.rb'
- 'ee/spec/features/projects/security/dast_site_profiles_spec.rb'
- 'ee/spec/features/projects/settings/ee/service_desk_setting_spec.rb'
- 'ee/spec/finders/billed_users_finder_spec.rb'
- 'ee/spec/finders/clusters/environments_finder_spec.rb'
- 'ee/spec/finders/dast/profiles_finder_spec.rb'
@ -274,15 +254,6 @@ Layout/SpaceInsideBlockBraces:
- 'lib/tasks/gitlab/praefect.rake'
- 'lib/tasks/gitlab/shell.rake'
- 'lib/tasks/gitlab/tw/codeowners.rake'
- 'qa/qa/service/praefect_manager.rb'
- 'qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb'
- 'qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb'
- 'qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/12_geo/geo_replication_npm_registry_spec.rb'
- 'qa/spec/scenario/test/integration/mattermost_spec.rb'
- 'qa/spec/support/page_error_checker_spec.rb'
- 'rubocop/cop/migration/add_limit_to_text_columns.rb'
- 'spec/config/settings_spec.rb'
- 'spec/controllers/admin/application_settings_controller_spec.rb'
@ -315,38 +286,6 @@ Layout/SpaceInsideBlockBraces:
- 'spec/factories/packages/packages.rb'
- 'spec/factories/prometheus_alert.rb'
- 'spec/factories/prometheus_metrics.rb'
- 'spec/features/admin/admin_mode/login_spec.rb'
- 'spec/features/admin/users/users_spec.rb'
- 'spec/features/boards/board_filters_spec.rb'
- 'spec/features/boards/reload_boards_on_browser_back_spec.rb'
- 'spec/features/dashboard/archived_projects_spec.rb'
- 'spec/features/error_tracking/user_filters_errors_by_status_spec.rb'
- 'spec/features/groups/issues_spec.rb'
- 'spec/features/groups_spec.rb'
- 'spec/features/issuables/user_sees_sidebar_spec.rb'
- 'spec/features/issues/gfm_autocomplete_spec.rb'
- 'spec/features/issues/todo_spec.rb'
- 'spec/features/issues/user_bulk_edits_issues_spec.rb'
- 'spec/features/issues/user_interacts_with_awards_spec.rb'
- 'spec/features/issues/user_uses_quick_actions_spec.rb'
- 'spec/features/merge_request/user_approves_spec.rb'
- 'spec/features/merge_request/user_customizes_merge_commit_message_spec.rb'
- 'spec/features/merge_request/user_edits_assignees_sidebar_spec.rb'
- 'spec/features/merge_request/user_sees_closing_issues_message_spec.rb'
- 'spec/features/merge_request/user_sees_deployment_widget_spec.rb'
- 'spec/features/merge_request/user_sees_diff_spec.rb'
- 'spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb'
- 'spec/features/merge_request/user_sees_versions_spec.rb'
- 'spec/features/merge_request/user_uses_quick_actions_spec.rb'
- 'spec/features/profiles/user_edit_profile_spec.rb'
- 'spec/features/projects/cluster_agents_spec.rb'
- 'spec/features/projects/commits/user_browses_commits_spec.rb'
- 'spec/features/projects/environments/environment_spec.rb'
- 'spec/features/projects/files/user_browses_files_spec.rb'
- 'spec/features/projects/pipelines/pipelines_spec.rb'
- 'spec/features/projects/settings/service_desk_setting_spec.rb'
- 'spec/features/projects/tree/tree_show_spec.rb'
- 'spec/features/users/login_spec.rb'
- 'spec/finders/ci/jobs_finder_spec.rb'
- 'spec/finders/ci/runners_finder_spec.rb'
- 'spec/finders/concerns/packages/finder_helper_spec.rb'

View file

@ -135,27 +135,8 @@
}
@include media-breakpoint-down(md) {
$controls-margin: $btn-margin-5 - 2px;
flex: 0 0 100%;
margin-top: $gl-padding-8;
.controls-item,
.controls-item-full,
.controls-item:last-child {
flex: 1 1 35%;
display: block;
width: 100%;
margin: $controls-margin;
.btn,
.dropdown {
margin: 0;
}
}
.controls-item-full {
flex: 1 1 100%;
}
}
@include media-breakpoint-down(sm) {

View file

@ -454,7 +454,6 @@ $default-icon-size: 16px;
$layout-link-gray: #7e7c7c;
$btn-side-margin: $grid-size;
$btn-sm-side-margin: 7px;
$btn-margin-5: 5px;
$count-arrow-border: #dce0e5;
$general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;

View file

@ -121,15 +121,6 @@
border-radius: $label-border-radius;
padding-top: $gl-vert-padding;
padding-bottom: $gl-vert-padding;
.icon svg {
position: relative;
top: 2px;
margin-right: $btn-margin-5;
width: $gl-font-size;
height: $gl-font-size;
fill: $orange-600;
}
}
}

View file

@ -523,10 +523,14 @@ module Ci
self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
end
def environment_deployment_tier
def environment_tier_from_options
self.options.dig(:environment, :deployment_tier) if self.options
end
def environment_tier
environment_tier_from_options || persisted_environment.try(:tier)
end
def triggered_by?(current_user)
user == current_user
end
@ -581,6 +585,7 @@ module Ci
variables.concat(persisted_environment.predefined_variables)
variables.append(key: 'CI_ENVIRONMENT_ACTION', value: environment_action)
variables.append(key: 'CI_ENVIRONMENT_TIER', value: environment_tier)
# Here we're passing unexpanded environment_url for runner to expand,
# and we need to make sure that CI_ENVIRONMENT_NAME and

View file

@ -438,7 +438,7 @@ class Deployment < ApplicationRecord
def tier_in_yaml
return unless deployable
deployable.environment_deployment_tier
deployable.environment_tier_from_options
end
private

View file

@ -253,7 +253,6 @@ class Environment < ApplicationRecord
Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_ENVIRONMENT_NAME', value: name)
.append(key: 'CI_ENVIRONMENT_SLUG', value: slug)
.append(key: 'CI_ENVIRONMENT_TIER', value: tier)
end
def recently_updated_on_branch?(ref)

View file

@ -84,7 +84,7 @@ module Deployments
def renew_deployment_tier
return unless deployable
if (tier = deployable.environment_deployment_tier)
if (tier = deployable.environment_tier_from_options)
environment.tier = tier
end
end

View file

@ -22,7 +22,7 @@ module GoogleCloud
def unique_gcp_project_ids
filter_params = { key: 'GCP_PROJECT_ID' }
::Ci::VariablesFinder.new(project, filter_params).execute.map(&:value).uniq
@unique_gcp_project_ids ||= ::Ci::VariablesFinder.new(project, filter_params).execute.map(&:value).uniq
end
def group_vars_by_environment(keys)

View file

@ -0,0 +1,74 @@
# frozen_string_literal: true
module GoogleCloud
DEFAULT_REGION = 'us-east1'
class CreateCloudsqlInstanceService < ::GoogleCloud::BaseService
WORKER_INTERVAL = 30.seconds
def execute
create_cloud_instance
trigger_instance_setup_worker
success
rescue Google::Apis::Error => err
error(err.to_json)
end
private
def create_cloud_instance
google_api_client.create_cloudsql_instance(gcp_project_id,
instance_name,
root_password,
database_version,
region,
tier)
end
def trigger_instance_setup_worker
GoogleCloud::CreateCloudsqlInstanceWorker.perform_in(WORKER_INTERVAL,
current_user.id,
project.id,
{
'google_oauth2_token': google_oauth2_token,
'gcp_project_id': gcp_project_id,
'instance_name': instance_name,
'database_version': database_version,
'environment_name': environment_name,
'is_protected': protected?
})
end
def protected?
project.protected_for?(environment_name)
end
def instance_name
# Generates an `instance_name` for the to-be-created Cloud SQL instance
# Example: `gitlab-34647-postgres-14-staging`
environment_alias = environment_name == '*' ? 'ALL' : environment_name
name = "gitlab-#{project.id}-#{database_version}-#{environment_alias}"
name.tr("_", "-").downcase
end
def root_password
SecureRandom.hex(16)
end
def database_version
params[:database_version]
end
def region
region = ::Ci::VariablesFinder
.new(project, { key: Projects::GoogleCloud::GcpRegionsController::GCP_REGION_CI_VAR_KEY,
environment_scope: environment_name })
.execute.first
region&.value || DEFAULT_REGION
end
def tier
params[:tier]
end
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module GoogleCloud
class EnableCloudsqlService < ::GoogleCloud::BaseService
def execute
return no_projects_error if unique_gcp_project_ids.empty?
unique_gcp_project_ids.each do |gcp_project_id|
google_api_client.enable_cloud_sql_admin(gcp_project_id)
google_api_client.enable_compute(gcp_project_id)
google_api_client.enable_service_networking(gcp_project_id)
end
success({ gcp_project_ids: unique_gcp_project_ids })
end
private
def no_projects_error
error("No GCP projects found. Configure a service account or GCP_PROJECT_ID CI variable.")
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module GoogleCloud
class GetCloudsqlInstancesService < ::GoogleCloud::BaseService
CLOUDSQL_KEYS = %w[GCP_PROJECT_ID GCP_CLOUDSQL_INSTANCE_NAME GCP_CLOUDSQL_VERSION].freeze
def execute
group_vars_by_environment(CLOUDSQL_KEYS).map do |environment_scope, value|
{
ref: environment_scope,
gcp_project: value['GCP_PROJECT_ID'],
instance_name: value['GCP_CLOUDSQL_INSTANCE_NAME'],
version: value['GCP_CLOUDSQL_VERSION']
}
end
end
end
end

View file

@ -16,29 +16,29 @@ module GoogleCloud
return error("CloudSQL instance not RUNNABLE: #{get_instance_response.to_json}")
end
database_response = google_api_client.create_cloudsql_database(gcp_project_id, instance_name, database_name)
save_instance_ci_vars(get_instance_response)
if database_response.status != OPERATION_STATE_DONE
return error("Database creation failed: #{database_response.to_json}")
list_database_response = google_api_client.list_cloudsql_databases(gcp_project_id, instance_name)
list_user_response = google_api_client.list_cloudsql_users(gcp_project_id, instance_name)
existing_database = list_database_response.items.find { |database| database.name == database_name }
existing_user = list_user_response.items.find { |user| user.name == username }
if existing_database && existing_user
save_database_ci_vars
save_user_ci_vars(existing_user)
return success
end
user_response = google_api_client.create_cloudsql_user(gcp_project_id, instance_name, username, password)
database_response = execute_database_setup(existing_database)
return database_response if database_response[:status] == :error
if user_response.status != OPERATION_STATE_DONE
return error("User creation failed: #{user_response.to_json}")
end
save_database_ci_vars
primary_ip_address = get_instance_response.ip_addresses.first.ip_address
connection_name = get_instance_response.connection_name
user_response = execute_user_setup(existing_user)
return user_response if user_response[:status] == :error
save_ci_var('GCP_PROJECT_ID', gcp_project_id)
save_ci_var('GCP_CLOUDSQL_INSTANCE_NAME', instance_name)
save_ci_var('GCP_CLOUDSQL_CONNECTION_NAME', connection_name)
save_ci_var('GCP_CLOUDSQL_PRIMARY_IP_ADDRESS', primary_ip_address)
save_ci_var('GCP_CLOUDSQL_VERSION', database_version)
save_ci_var('GCP_CLOUDSQL_DATABASE_NAME', database_name)
save_ci_var('GCP_CLOUDSQL_DATABASE_USER', username)
save_ci_var('GCP_CLOUDSQL_DATABASE_PASS', password, true)
save_user_ci_vars(existing_user)
success
rescue Google::Apis::Error => err
@ -64,11 +64,55 @@ module GoogleCloud
end
def password
SecureRandom.hex(16)
@password ||= SecureRandom.hex(16)
end
def save_ci_var(key, value, is_masked = false)
create_or_replace_project_vars(environment_name, key, value, @params[:is_protected], is_masked)
end
def save_instance_ci_vars(cloudsql_instance)
primary_ip_address = cloudsql_instance.ip_addresses.first.ip_address
connection_name = cloudsql_instance.connection_name
save_ci_var('GCP_PROJECT_ID', gcp_project_id)
save_ci_var('GCP_CLOUDSQL_INSTANCE_NAME', instance_name)
save_ci_var('GCP_CLOUDSQL_CONNECTION_NAME', connection_name)
save_ci_var('GCP_CLOUDSQL_PRIMARY_IP_ADDRESS', primary_ip_address)
save_ci_var('GCP_CLOUDSQL_VERSION', database_version)
end
def save_database_ci_vars
save_ci_var('GCP_CLOUDSQL_DATABASE_NAME', database_name)
end
def save_user_ci_vars(user_exists)
save_ci_var('GCP_CLOUDSQL_DATABASE_USER', username)
save_ci_var('GCP_CLOUDSQL_DATABASE_PASS', user_exists ? user_exists.password : password, true)
end
def execute_database_setup(database_exists)
return success if database_exists
database_response = google_api_client.create_cloudsql_database(gcp_project_id, instance_name, database_name)
if database_response.status != OPERATION_STATE_DONE
return error("Database creation failed: #{database_response.to_json}")
end
success
end
def execute_user_setup(existing_user)
return success if existing_user
user_response = google_api_client.create_cloudsql_user(gcp_project_id, instance_name, username, password)
if user_response.status != OPERATION_STATE_DONE
return error("User creation failed: #{user_response.to_json}")
end
success
end
end
end

View file

@ -8,4 +8,4 @@
- title = s_('TagsPage|Only a project maintainer or owner can delete a protected tag')
- disabled = true
= render Pajamas::ButtonComponent.new(variant: :default, icon: 'remove', button_options: { class: "js-delete-tag-button gl-ml-3\!", 'aria-label': s_('TagsPage|Delete tag'), title: title, disabled: disabled, data: { toggle: 'tooltip', container: 'body', path: project_tag_path(@project, tag.name), tag_name: tag.name, is_protected: protected_tag?(project, tag).to_s } })
= render Pajamas::ButtonComponent.new(variant: :default, icon: 'remove', button_options: { class: "js-delete-tag-button", 'aria-label': s_('TagsPage|Delete tag'), title: title, disabled: disabled, data: { toggle: 'tooltip', container: 'body', path: project_tag_path(@project, tag.name), tag_name: tag.name, is_protected: protected_tag?(project, tag).to_s } })

View file

@ -1,7 +1,9 @@
- release_btn_text = s_('TagsPage|Create release')
- release_btn_path = new_project_release_path(project, tag_name: tag.name)
- option_css_classes = local_assigns.fetch(:option_css_classes, '')
- css_classes = "btn gl-button btn-default btn-icon btn-edit has-tooltip #{option_css_classes}"
- if release
- release_btn_text = s_('TagsPage|Edit release')
- release_btn_path = edit_project_release_path(project, release)
= link_to release_btn_path, class: 'btn gl-button btn-default btn-icon btn-edit has-tooltip', title: release_btn_text, data: { container: "body" } do
= link_to release_btn_path, class: css_classes, title: release_btn_text, data: { container: "body" } do
= sprite_icon('pencil', css_class: 'gl-icon')

View file

@ -40,5 +40,5 @@
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :admin_tag, @project)
= render 'edit_release_button', tag: tag, project: @project, release: release
= render 'edit_release_button', tag: tag, project: @project, release: release, option_css_classes: 'gl-mr-3!'
= render 'projects/buttons/remove_tag', project: @project, tag: tag

View file

@ -42,15 +42,13 @@
= render partial: 'projects/commit/signature', object: @tag.signature
- if can?(current_user, :admin_tag, @project)
= render 'edit_release_button', tag: @tag, project: @project, release: @release
= link_to project_tree_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default controls-item has-tooltip', title: s_('TagsPage|Browse files') do
= link_to project_tree_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default has-tooltip', title: s_('TagsPage|Browse files') do
= sprite_icon('folder-open', css_class: 'gl-icon')
= link_to project_commits_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
= link_to project_commits_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default has-tooltip', title: s_('TagsPage|Browse commits') do
= sprite_icon('history', css_class: 'gl-icon')
.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_tag, @project)
.btn-container.controls-item-full
= render 'projects/buttons/remove_tag', project: @project, tag: @tag
= render 'projects/buttons/remove_tag', project: @project, tag: @tag
- if @tag.message.present?
%pre.wrap{ data: { qa_selector: 'tag_message_content' } }

View file

@ -0,0 +1,14 @@
---
# Error: gitlab.MultiLineLinks
#
# Checks that links are all on a single line.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Link "%s" must be on a single line, even if very long.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#basic-link-criteria
level: warning
scope: raw
raw:
- '\[[^\]]*?\n[^\]]*?\]\([^\)]*?\)|'
- '\[[^\]]*?\]\([^\)]*?\n[^\)]*\)'

View file

@ -135,8 +135,7 @@ stores more than 600 gigabytes of data, and `ci_builds.yaml_variables` more
than 300 gigabytes (as of February 2021).
It is a lot of data that needs to be reliably moved to a different place.
Unfortunately, right now, our [background
migrations](https://docs.gitlab.com/ee/development/background_migrations.html)
Unfortunately, right now, our [background migrations](../../../development/database/background_migrations.md)
are not reliable enough to migrate this amount of data at scale. We need to
build mechanisms that will give us confidence in moving this data between
columns, tables, partitions or database shards.

View file

@ -20,8 +20,7 @@ GraphQL development and helped to surface the need of improving tooling we use
to extend the new API.
This document describes the work that is needed to build a stable foundation that
will support our development efforts and a large-scale usage of the [GraphQL
API](https://docs.gitlab.com/ee/api/graphql/index.html).
will support our development efforts and a large-scale usage of the [GraphQL API](../../../api/graphql/index.md).
## Summary

View file

@ -93,9 +93,8 @@ class RemoveUsersUpdatedAtColumn < Gitlab::Database::Migration[2.0]
end
```
You can consider [enabling lock retries](
https://docs.gitlab.com/ee/development/migration_style_guide.html#usage-with-transactional-migrations
) when you run a migration on big tables, because it might take some time to
You can consider [enabling lock retries](../migration_style_guide.md#usage-with-transactional-migrations)
when you run a migration on big tables, because it might take some time to
acquire a lock on this table.
#### B. The removed column has an index or constraint that belongs to it
@ -126,13 +125,11 @@ end
In the `down` method, we check to see if the column already exists before adding it again.
We do this because the migration is non-transactional and might have failed while it was running.
The [`disable_ddl_transaction!`](
https://docs.gitlab.com/ee/development/migration_style_guide.html#usage-with-non-transactional-migrations-disable_ddl_transaction
) is used to disable the transaction that wraps the whole migration.
The [`disable_ddl_transaction!`](../migration_style_guide.md#usage-with-non-transactional-migrations-disable_ddl_transaction)
is used to disable the transaction that wraps the whole migration.
You can refer to the page [Migration Style Guide](
https://docs.gitlab.com/ee/development/migration_style_guide.html
) for more information about database migrations.
You can refer to the page [Migration Style Guide](../migration_style_guide.md)
for more information about database migrations.
### Step 3: Removing the ignore rule (release M+2)
@ -295,8 +292,7 @@ when migrating a column in a large table (for example, `issues`). Background
migrations spread the work / load over a longer time period, without slowing
down deployments.
For more information, see [the documentation on cleaning up background
migrations](background_migrations.md#cleaning-up).
For more information, see [the documentation on cleaning up background migrations](background_migrations.md#cleaning-up).
## Adding Indexes

View file

@ -395,8 +395,7 @@ We considered using these Rails features as an alternative to foreign keys but t
For non-trivial objects that need to clean up data outside the
database (for example, object storage) where you might wish to use `dependent: :destroy`,
see alternatives in
[Avoid `dependent: :nullify` and `dependent: :destroy` across
databases](./multiple_databases.md#avoid-dependent-nullify-and-dependent-destroy-across-databases).
[Avoid `dependent: :nullify` and `dependent: :destroy` across databases](multiple_databases.md#avoid-dependent-nullify-and-dependent-destroy-across-databases).
## Risks of loose foreign keys and possible mitigations

View file

@ -722,10 +722,12 @@ We include guidance for links in these categories:
- Use inline link Markdown markup `[Text](https://example.com)`.
It's easier to read, review, and maintain. Do not use `[Text][identifier]` reference-style links.
- Use meaningful anchor text.
For example, instead of writing something like `Read more about merge requests [here](LINK)`,
write `Read more about [merge requests](LINK)`.
- Put the entire link on a single line. Some of our [linters](../testing.md) do not
validate links when split over multiple lines, and incorrect or broken links could
slip through.
### Links to internal documentation

View file

@ -65,7 +65,7 @@ module Gitlab
fields.merge!(
environment: environment.name,
environment_protected: environment_protected?.to_s,
deployment_tier: build.environment_deployment_tier || environment.tier
deployment_tier: build.environment_tier
)
end

View file

@ -30,7 +30,7 @@ module Gitlab
end
def deployment_tier
job.environment_deployment_tier
job.environment_tier_from_options
end
def expanded_environment_name

View file

@ -148,16 +148,42 @@ module GoogleApi
enable_service(gcp_project_id, 'cloudbuild.googleapis.com')
end
def enable_cloud_sql_admin(gcp_project_id)
enable_service(gcp_project_id, 'sqladmin.googleapis.com')
end
def enable_compute(gcp_project_id)
enable_service(gcp_project_id, 'compute.googleapis.com')
end
def enable_service_networking(gcp_project_id)
enable_service(gcp_project_id, 'servicenetworking.googleapis.com')
end
def revoke_authorizations
uri = URI(REVOKE_URL)
Gitlab::HTTP.post(uri, body: { 'token' => access_token })
end
def list_cloudsql_databases(gcp_project_id, instance_name)
service = Google::Apis::SqladminV1beta4::SQLAdminService.new
service.authorization = access_token
service.list_databases(gcp_project_id, instance_name, options: user_agent_header)
end
def create_cloudsql_database(gcp_project_id, instance_name, database_name)
database = Google::Apis::SqladminV1beta4::Database.new(name: database_name)
sql_admin_service.insert_database(gcp_project_id, instance_name, database)
end
def list_cloudsql_users(gcp_project_id, instance_name)
service = Google::Apis::SqladminV1beta4::SQLAdminService.new
service.authorization = access_token
service.list_users(gcp_project_id, instance_name, options: user_agent_header)
end
def create_cloudsql_user(gcp_project_id, instance_name, username, password)
user = Google::Apis::SqladminV1beta4::User.new
user.name = username
@ -169,6 +195,20 @@ module GoogleApi
sql_admin_service.get_instance(gcp_project_id, instance_name)
end
def create_cloudsql_instance(gcp_project_id, instance_name, root_password, database_version, region, tier)
database_instance = Google::Apis::SqladminV1beta4::DatabaseInstance.new(
name: instance_name,
root_password: root_password,
database_version: database_version,
region: region,
settings: Google::Apis::SqladminV1beta4::Settings.new(tier: tier)
)
service = Google::Apis::SqladminV1beta4::SQLAdminService.new
service.authorization = access_token
service.insert_instance(gcp_project_id, database_instance)
end
private
def enable_service(gcp_project_id, service_name)

View file

@ -425,7 +425,7 @@ module QA
end
def value_for_node(data, node)
data.find(-> {{ value: 0 }}) { |item| item[:node] == node }[:value]
data.find(-> { { value: 0 } }) { |item| item[:node] == node }[:value]
end
def wait_for_replication(project_id)

View file

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Plan', :reliable do
describe 'Custom issue templates' do
let(:template_name) { 'custom_issue_template'}
let(:template_name) { 'custom_issue_template' }
let(:template_content) { 'This is a custom issue template test' }
let(:template_project) do

View file

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create', :reliable do
describe 'Merge request custom templates' do
let(:template_name) { 'custom_merge_request_template'}
let(:template_name) { 'custom_merge_request_template' }
let(:template_content) { 'This is a custom merge request template test' }
let(:template_project) do
Resource::Project.fabricate_via_api! do |project|

View file

@ -28,16 +28,16 @@ module QA
context 'on a project with a commonly used LICENSE',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366842' do
it_behaves_like 'project license detection' do
let(:license_file_name) {'bsd-3-clause'}
let(:rendered_license_name) {'BSD 3-Clause "New" or "Revised" License'}
let(:license_file_name) { 'bsd-3-clause' }
let(:rendered_license_name) { 'BSD 3-Clause "New" or "Revised" License' }
end
end
context 'on a project with a less commonly used LICENSE',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366843' do
it_behaves_like 'project license detection' do
let(:license_file_name) {'GFDL-1.2-only'}
let(:rendered_license_name) {'Other'}
let(:license_file_name) { 'GFDL-1.2-only' }
let(:rendered_license_name) { 'Other' }
end
end
end

View file

@ -26,7 +26,7 @@ module QA
end
let(:flag) { Pathname.new(file.path).basename('.yml').to_s }
let(:root) { '..'}
let(:root) { '..' }
before do
definition = <<~YAML
@ -78,7 +78,7 @@ module QA
end
context 'with an EE feature flag' do
let(:root) { '../ee'}
let(:root) { '../ee' }
include_examples 'gets flag value'
end

View file

@ -121,7 +121,7 @@ RSpec.describe 'Admin Mode Login' do
end
context 'when logging in via omniauth' do
let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false)}
let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false) }
let(:mock_saml_response) do
File.read('spec/fixtures/authentication/saml_response.xml')
end

View file

@ -357,7 +357,7 @@ RSpec.describe 'Admin::Users' do
end
it 'creates new user' do
expect { click_button 'Create user' }.to change {User.count}.by(1)
expect { click_button 'Create user' }.to change { User.count }.by(1)
end
it 'applies defaults to user' do
@ -400,7 +400,7 @@ RSpec.describe 'Admin::Users' do
let_it_be(:user_username) { 'Bing bang' }
it "doesn't create the user and shows an error message" do
expect { click_button 'Create user' }.to change {User.count}.by(0)
expect { click_button 'Create user' }.to change { User.count }.by(0)
expect(page).to have_content('The form contains the following error')
expect(page).to have_content('Username can contain only letters, digits')

View file

@ -16,7 +16,7 @@ RSpec.describe 'Issue board filters', :js do
let_it_be(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue_1) }
let(:filtered_search) { find('[data-testid="issue-board-filtered-search"]') }
let(:filter_input) { find('.gl-filtered-search-term-input')}
let(:filter_input) { find('.gl-filtered-search-term-input') }
let(:filter_dropdown) { find('.gl-filtered-search-suggestion-list') }
let(:filter_first_suggestion) { find('.gl-filtered-search-suggestion-list').first('.gl-filtered-search-suggestion') }
let(:filter_submit) { find('.gl-search-box-by-click-search-button') }
@ -164,7 +164,7 @@ RSpec.describe 'Issue board filters', :js do
end
describe 'filters by type' do
let_it_be(:incident) { create(:incident, project: project)}
let_it_be(:incident) { create(:incident, project: project) }
before do
set_filter('type')

View file

@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do
let(:project) {create(:project, :public)}
let(:board) {create(:board, project: project)}
let(:user) {create(:user)}
let(:project) { create(:project, :public) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
context 'authorized user' do
before do

View file

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'Dashboard Archived Project' do
let(:user) { create :user }
let(:project) { create :project}
let(:project) { create :project }
let(:archived_project) { create(:project, :archived) }
before do

View file

@ -10,8 +10,8 @@ RSpec.describe 'When a user filters Sentry errors by status', :js, :use_clean_ra
let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" }
let(:issues_api_url_filter) { "#{sentry_api_urls.issues_url}?limit=20&query=is:ignored" }
let(:auth_token) {{ 'Authorization' => 'Bearer access_token_123' }}
let(:return_header) {{ 'Content-Type' => 'application/json' }}
let(:auth_token) { { 'Authorization' => 'Bearer access_token_123' } }
let(:return_header) { { 'Content-Type' => 'application/json' } }
before do
stub_request(:get, issues_api_url).with(headers: auth_token)

View file

@ -7,12 +7,12 @@ RSpec.describe 'Group issues page' do
include DragTo
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group)}
let(:project) { create(:project, :public, group: group) }
let(:project_with_issues_disabled) { create(:project, :issues_disabled, group: group) }
let(:path) { issues_group_path(group) }
context 'with shared examples', :js do
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
let(:issuable) { create(:issue, project: project, title: "this is my created issuable") }
include_examples 'project features apply to issuables', Issue
@ -68,7 +68,7 @@ RSpec.describe 'Group issues page' do
context 'issues list', :js do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let(:subgroup_project) { create(:project, :public, group: subgroup) }
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let!(:issue) { create(:issue, project: project, title: 'root group issue') }
let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }

View file

@ -378,7 +378,7 @@ RSpec.describe 'Group' do
end
it 'removes group', :sidekiq_might_not_need_inline do
expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
expect { remove_with_confirm('Remove group', group.path) }.to change { Group.count }.by(-1)
expect(group.members.all.count).to be_zero
expect(page).to have_content "scheduled for deletion"
end

View file

@ -8,7 +8,7 @@ RSpec.describe 'Issue Sidebar on Mobile' do
let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
let!(:user) { create(:user) }
before do
sign_in(user)

View file

@ -414,7 +414,7 @@ RSpec.describe 'GFM autocomplete', :js do
it 'shows all contacts' do
page.within(find_autocomplete_menu) do
expected_data = contacts.map { |c| "#{c.first_name} #{c.last_name} #{c.email}"}
expected_data = contacts.map { |c| "#{c.first_name} #{c.last_name} #{c.email}" }
expect(page.all('li').map(&:text)).to match_array(expected_data)
end

View file

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Manually create a todo item from issue', :js do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
let!(:user) { create(:user) }
before do
project.add_maintainer(user)

View file

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Multiple issue updating from issues#index', :js do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
let!(:user) { create(:user) }
before do
project.add_maintainer(user)

View file

@ -6,7 +6,7 @@ RSpec.describe 'User interacts with awards' do
let(:user) { create(:user) }
describe 'User interacts with awards in an issue', :js do
let(:issue) { create(:issue, project: project)}
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
before do

View file

@ -18,7 +18,7 @@ RSpec.describe 'Issues > User uses quick actions', :js do
let!(:label_feature) { create(:label, project: project, title: 'feature') }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:issue, project: project) }
let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature]) }
it_behaves_like 'close quick action', :issue
it_behaves_like 'issuable time tracker', :issue

View file

@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User approves', :js do
def verify_approvals_count_on_index!
visit(project_merge_requests_path(project, state: :all))
expect(page.all('li').any? { |item| item["title"] == "1 approver (you've approved)"}).to be true
expect(page.all('li').any? { |item| item["title"] == "1 approver (you've approved)" }).to be true
visit project_merge_request_path(project, merge_request)
end

View file

@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Merge request < User customizes merge commit message', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:issue_1) { create(:issue, project: project) }
let(:issue_2) { create(:issue, project: project) }
let(:source_branch) { 'csv' }
let(:target_branch) { 'master' }
let(:squash) { false }

View file

@ -89,7 +89,7 @@ RSpec.describe 'Merge request > User edits assignees sidebar', :js do
context 'when GraphQL assignees widget feature flag is enabled' do
let(:sidebar_assignee_dropdown_item) { sidebar_assignee_block.find(".dropdown-item", text: assignee.username ) }
let(:sidebar_assignee_dropdown_tooltip) { sidebar_assignee_dropdown_item['title']}
let(:sidebar_assignee_dropdown_tooltip) { sidebar_assignee_dropdown_item['title'] }
context 'when user is an owner' do
before do

View file

@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Merge request > User sees closing issues message', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:issue_1) { create(:issue, project: project) }
let(:issue_2) { create(:issue, project: project) }
let(:merge_request) do
create(
:merge_request,

View file

@ -14,7 +14,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
let(:ref) { merge_request.target_branch }
let(:sha) { project.commit(ref).id }
let(:pipeline) { create(:ci_pipeline, sha: sha, project: project, ref: ref) }
let!(:manual) { }
let!(:manual) {}
let(:build) { create(:ci_build, :with_deployment, environment: environment.name, pipeline: pipeline) }
let!(:deployment) { build.deployment }

View file

@ -86,7 +86,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
context 'when file contains html' do
let(:current_user) { project.first_owner }
let(:branch_name) {"test_branch"}
let(:branch_name) { "test_branch" }
it 'escapes any HTML special characters in the diff chunk header' do
file_content =

View file

@ -25,7 +25,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
}
end
let(:expected_detached_mr_tag) {'merge request'}
let(:expected_detached_mr_tag) { 'merge request' }
before do
stub_application_setting(auto_devops_enabled: false)

View file

@ -232,7 +232,7 @@ RSpec.describe 'Merge request > User sees versions', :js do
end
it 'only shows diffs from the commit' do
diff_commit_ids = find_all('.diff-file [data-commit-id]').map {|diff| diff['data-commit-id']}
diff_commit_ids = find_all('.diff-file [data-commit-id]').map { |diff| diff['data-commit-id'] }
expect(diff_commit_ids).not_to be_empty
expect(diff_commit_ids).to all(eq(params[:commit_id]))

View file

@ -24,7 +24,7 @@ RSpec.describe 'Merge request > User uses quick actions', :js do
let!(:label_feature) { create(:label, project: project, title: 'feature') }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:merge_request, source_project: project) }
let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature]) }
it_behaves_like 'close quick action', :merge_request
it_behaves_like 'issuable time tracker', :merge_request

View file

@ -294,7 +294,7 @@ RSpec.describe 'User edit profile' do
end
context 'user menu' do
let(:issue) { create(:issue, project: project)}
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
def open_modal(button_text)
@ -536,7 +536,7 @@ RSpec.describe 'User edit profile' do
end
context 'User time preferences', :js do
let(:issue) { create(:issue, project: project)}
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
before do

View file

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'ClusterAgents', :js do
let_it_be(:token) { create(:cluster_agent_token, description: 'feature test token')}
let_it_be(:token) { create(:cluster_agent_token, description: 'feature test token') }
let(:agent) { token.agent }
let(:project) { agent.project }

View file

@ -150,7 +150,7 @@ RSpec.describe 'User browses commits' do
let(:ref) { project.repository.root_ref }
let(:newrev) { project.repository.commit('master').sha }
let(:short_newrev) { project.repository.commit('master').short_id }
let(:message) { 'Glob characters'}
let(:message) { 'Glob characters' }
before do
create_file_in_repo(project, ref, ref, filename, 'Test file', commit_message: message)

View file

@ -18,10 +18,10 @@ RSpec.describe 'Environment' do
describe 'environment details page' do
let!(:environment) { create(:environment, project: project) }
let!(:permissions) { }
let!(:deployment) { }
let!(:action) { }
let!(:cluster) { }
let!(:permissions) {}
let!(:deployment) {}
let!(:action) {}
let!(:cluster) {}
context 'with auto-stop' do
let!(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) }

View file

@ -348,7 +348,7 @@ RSpec.describe "User browses files", :js do
end
it "shows raw file content in a new tab" do
new_tab = window_opened_by {click_link 'Open raw'}
new_tab = window_opened_by { click_link 'Open raw' }
within_window new_tab do
expect(page).to have_content("Test file")
@ -366,7 +366,7 @@ RSpec.describe "User browses files", :js do
end
it "shows raw file content in a new tab" do
new_tab = window_opened_by {click_link 'Open raw'}
new_tab = window_opened_by { click_link 'Open raw' }
within_window new_tab do
expect(page).to have_content("*.rbc")

View file

@ -162,7 +162,7 @@ RSpec.describe 'Project > Members > Manage groups', :js do
let_it_be(:user) { maintainer }
let_it_be(:group) { parent_group }
let_it_be(:group_within_hierarchy) { create(:group, parent: group) }
let_it_be(:project_within_hierarchy) { create(:project, group: group_within_hierarchy)}
let_it_be(:project_within_hierarchy) { create(:project, group: group_within_hierarchy) }
let_it_be(:members_page_path) { project_project_members_path(project) }
let_it_be(:members_page_path_within_hierarchy) { project_project_members_path(project_within_hierarchy) }
end

View file

@ -7,7 +7,7 @@ RSpec.describe 'Pipelines', :js do
include Spec::Support::Helpers::ModalHelpers
let(:project) { create(:project) }
let(:expected_detached_mr_tag) {'merge request'}
let(:expected_detached_mr_tag) { 'merge request' }
context 'when user is logged in' do
let(:user) { create(:user) }

View file

@ -7,7 +7,7 @@ RSpec.describe 'Pipelines', :js do
include Spec::Support::Helpers::ModalHelpers
let(:project) { create(:project) }
let(:expected_detached_mr_tag) {'merge request'}
let(:expected_detached_mr_tag) { 'merge request' }
context 'when user is logged in' do
let(:user) { create(:user) }

View file

@ -81,7 +81,7 @@ RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache do
}
end
let_it_be_with_reload(:group) { create(:group)}
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: issuable_project_template_files) }
let_it_be(:group_template_repo) { create(:project, :custom_repo, group: group, files: issuable_group_template_files) }

View file

@ -54,7 +54,7 @@ RSpec.describe 'Projects tree', :js do
let(:filename) { File.join(path, 'test.txt') }
let(:newrev) { project.repository.commit('master').sha }
let(:short_newrev) { project.repository.commit('master').short_id }
let(:message) { 'Glob characters'}
let(:message) { 'Glob characters' }
before do
create_file_in_repo(project, 'master', 'master', filename, 'Test file', commit_message: message)

View file

@ -365,7 +365,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
end
context 'when logging in via OAuth' do
let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')}
let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') }
let(:mock_saml_response) do
File.read('spec/fixtures/authentication/saml_response.xml')
end

View file

@ -10,6 +10,25 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
let(:gcp_project_id) { String('gcp_proj_id') }
let(:operation) { true }
let(:database_instance) { Google::Apis::SqladminV1beta4::DatabaseInstance.new(state: 'RUNNABLE') }
let(:instance_name) { 'mock-instance-name' }
let(:root_password) { 'mock-root-password' }
let(:database_version) { 'mock-database-version' }
let(:region) { 'mock-region' }
let(:tier) { 'mock-tier' }
let(:database_list) do
Google::Apis::SqladminV1beta4::ListDatabasesResponse.new(items: [
Google::Apis::SqladminV1beta4::Database.new(name: 'db_01', instance: database_instance),
Google::Apis::SqladminV1beta4::Database.new(name: 'db_02', instance: database_instance)
])
end
let(:user_list) do
Google::Apis::SqladminV1beta4::ListUsersResponse.new(items: [
Google::Apis::SqladminV1beta4::User.new(name: 'user_01', instance: database_instance),
Google::Apis::SqladminV1beta4::User.new(name: 'user_02', instance: database_instance)
])
end
describe '.session_key_for_redirect_uri' do
let(:state) { 'random_string' }
@ -342,6 +361,42 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
end
end
describe '#enable_cloud_sql_admin' do
subject { client.enable_cloud_sql_admin(gcp_project_id) }
it 'calls Google Api ServiceUsageService' do
expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
.to receive(:enable_service)
.with("projects/#{gcp_project_id}/services/sqladmin.googleapis.com")
.and_return(operation)
is_expected.to eq(operation)
end
end
describe '#enable_compute' do
subject { client.enable_compute(gcp_project_id) }
it 'calls Google Api ServiceUsageService' do
expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
.to receive(:enable_service)
.with("projects/#{gcp_project_id}/services/compute.googleapis.com")
.and_return(operation)
is_expected.to eq(operation)
end
end
describe '#enable_service_networking' do
subject { client.enable_service_networking(gcp_project_id) }
it 'calls Google Api ServiceUsageService' do
expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
.to receive(:enable_service)
.with("projects/#{gcp_project_id}/services/servicenetworking.googleapis.com")
.and_return(operation)
is_expected.to eq(operation)
end
end
describe '#revoke_authorizations' do
subject { client.revoke_authorizations }
@ -393,4 +448,57 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
is_expected.to eq(database_instance)
end
end
describe '#list_cloudsql_databases' do
subject { client.list_cloudsql_databases(:gcp_project_id, :instance_name) }
it 'calls Google Api SQLAdminService#list_databases' do
expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
.to receive(:list_databases)
.with(any_args)
.and_return(database_list)
is_expected.to eq(database_list)
end
end
describe '#list_cloudsql_users' do
subject { client.list_cloudsql_users(:gcp_project_id, :instance_name) }
it 'calls Google Api SQLAdminService#list_users' do
expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
.to receive(:list_users)
.with(any_args)
.and_return(user_list)
is_expected.to eq(user_list)
end
end
describe '#create_cloudsql_instance' do
subject do
client.create_cloudsql_instance(
gcp_project_id,
instance_name,
root_password,
database_version,
region,
tier
)
end
it 'calls Google Api SQLAdminService#insert_instance' do
expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
.to receive(:insert_instance)
.with(gcp_project_id,
having_attributes(
class: ::Google::Apis::SqladminV1beta4::DatabaseInstance,
name: instance_name,
root_password: root_password,
database_version: database_version,
region: region,
settings: instance_of(Google::Apis::SqladminV1beta4::Settings)
))
.and_return(operation)
is_expected.to eq(operation)
end
end
end

View file

@ -1518,8 +1518,8 @@ RSpec.describe Ci::Build do
end
end
describe '#environment_deployment_tier' do
subject { build.environment_deployment_tier }
describe '#environment_tier_from_options' do
subject { build.environment_tier_from_options }
let(:build) { described_class.new(options: options) }
let(:options) { { environment: { deployment_tier: 'production' } } }
@ -1533,6 +1533,30 @@ RSpec.describe Ci::Build do
end
end
describe '#environment_tier' do
subject { build.environment_tier }
let(:options) { { environment: { deployment_tier: 'production' } } }
let!(:environment) { create(:environment, name: 'production', tier: 'development', project: project) }
let(:build) { described_class.new(options: options, environment: 'production', project: project) }
it { is_expected.to eq('production') }
context 'when options does not include deployment_tier' do
let(:options) { { environment: { name: 'production' } } }
it 'uses tier from environment' do
is_expected.to eq('development')
end
context 'when persisted environment is absent' do
let(:environment) { nil }
it { is_expected.to be_nil }
end
end
end
describe 'environment' do
describe '#has_environment?' do
subject { build.has_environment? }
@ -2921,7 +2945,7 @@ RSpec.describe Ci::Build do
let(:expected_variables) do
predefined_variables.map { |variable| variable.fetch(:key) } +
%w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL]
CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_TIER CI_ENVIRONMENT_URL]
end
before do
@ -3088,6 +3112,16 @@ RSpec.describe Ci::Build do
end
end
context 'when environment_tier is updated in options' do
before do
build.update!(options: { environment: { name: 'production', deployment_tier: 'development' } })
end
it 'uses tier from options' do
is_expected.to include({ key: 'CI_ENVIRONMENT_TIER', value: 'development', public: true, masked: false })
end
end
context 'when project has an environment specific variable' do
let(:environment_specific_variable) do
{ key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false }

View file

@ -0,0 +1,90 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GoogleCloud::CreateCloudsqlInstanceService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:gcp_project_id) { 'gcp_project_120' }
let(:environment_name) { 'test_env_42' }
let(:database_version) { 'POSTGRES_8000' }
let(:tier) { 'REIT_TIER' }
let(:service) do
described_class.new(project, user, {
gcp_project_id: gcp_project_id,
environment_name: environment_name,
database_version: database_version,
tier: tier
})
end
describe '#execute' do
before do
allow_next_instance_of(::Ci::VariablesFinder) do |variable_finder|
allow(variable_finder).to receive(:execute).and_return([])
end
end
it 'triggers creation of a cloudsql instance' do
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
expected_instance_name = "gitlab-#{project.id}-postgres-8000-test-env-42"
expect(client).to receive(:create_cloudsql_instance)
.with(gcp_project_id,
expected_instance_name,
String,
database_version,
'us-east1',
tier)
end
result = service.execute
expect(result[:status]).to be(:success)
end
it 'triggers worker to manage cloudsql instance creation operation results' do
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
expect(client).to receive(:create_cloudsql_instance)
end
expect(GoogleCloud::CreateCloudsqlInstanceWorker).to receive(:perform_in)
result = service.execute
expect(result[:status]).to be(:success)
end
context 'when google APIs fail' do
it 'returns error' do
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
expect(client).to receive(:create_cloudsql_instance).and_raise(Google::Apis::Error.new('mock-error'))
end
result = service.execute
expect(result[:status]).to eq(:error)
end
end
context 'when project has GCP_REGION defined' do
let(:gcp_region) { instance_double(::Ci::Variable, key: 'GCP_REGION', value: 'user-defined-region') }
before do
allow_next_instance_of(::Ci::VariablesFinder) do |variable_finder|
allow(variable_finder).to receive(:execute).and_return([gcp_region])
end
end
it 'uses defined region' do
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
expect(client).to receive(:create_cloudsql_instance)
.with(gcp_project_id,
String,
String,
database_version,
'user-defined-region',
tier)
end
service.execute
end
end
end
end

View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GoogleCloud::EnableCloudsqlService do
let_it_be(:project) { create(:project) }
subject(:result) { described_class.new(project).execute }
context 'when a project does not have any GCP_PROJECT_IDs configured' do
it 'returns error' do
message = 'No GCP projects found. Configure a service account or GCP_PROJECT_ID CI variable.'
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq(message)
end
end
context 'when a project has GCP_PROJECT_IDs configured' do
before do
project.variables.build(environment_scope: 'production', key: 'GCP_PROJECT_ID', value: 'prj-prod')
project.variables.build(environment_scope: 'staging', key: 'GCP_PROJECT_ID', value: 'prj-staging')
project.save!
end
it 'enables cloudsql, compute and service networking Google APIs', :aggregate_failures do
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
expect(instance).to receive(:enable_cloud_sql_admin).with('prj-prod')
expect(instance).to receive(:enable_compute).with('prj-prod')
expect(instance).to receive(:enable_service_networking).with('prj-prod')
expect(instance).to receive(:enable_cloud_sql_admin).with('prj-staging')
expect(instance).to receive(:enable_compute).with('prj-staging')
expect(instance).to receive(:enable_service_networking).with('prj-staging')
end
expect(result[:status]).to eq(:success)
end
end
end

View file

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GoogleCloud::GetCloudsqlInstancesService do
let(:service) { described_class.new(project) }
let(:project) { create(:project) }
context 'when project has no registered cloud sql instances' do
it 'result is empty' do
expect(service.execute.length).to eq(0)
end
end
context 'when project has registered cloud sql instance' do
before do
keys = %w[
GCP_PROJECT_ID
GCP_CLOUDSQL_INSTANCE_NAME
GCP_CLOUDSQL_CONNECTION_NAME
GCP_CLOUDSQL_PRIMARY_IP_ADDRESS
GCP_CLOUDSQL_VERSION
GCP_CLOUDSQL_DATABASE_NAME
GCP_CLOUDSQL_DATABASE_USER
GCP_CLOUDSQL_DATABASE_PASS
]
envs = %w[
*
STG
PRD
]
keys.each do |key|
envs.each do |env|
project.variables.build(protected: false, environment_scope: env, key: key, value: "value-#{key}-#{env}")
end
end
end
it 'result is grouped by environment', :aggregate_failures do
expect(service.execute).to contain_exactly({
ref: '*',
gcp_project: 'value-GCP_PROJECT_ID-*',
instance_name: 'value-GCP_CLOUDSQL_INSTANCE_NAME-*',
version: 'value-GCP_CLOUDSQL_VERSION-*'
},
{
ref: 'STG',
gcp_project: 'value-GCP_PROJECT_ID-STG',
instance_name: 'value-GCP_CLOUDSQL_INSTANCE_NAME-STG',
version: 'value-GCP_CLOUDSQL_VERSION-STG'
},
{
ref: 'PRD',
gcp_project: 'value-GCP_PROJECT_ID-PRD',
instance_name: 'value-GCP_CLOUDSQL_INSTANCE_NAME-PRD',
version: 'value-GCP_CLOUDSQL_VERSION-PRD'
})
end
end
end

View file

@ -5,6 +5,21 @@ require 'spec_helper'
RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do
let(:random_user) { create(:user) }
let(:project) { create(:project) }
let(:list_databases_empty) { Google::Apis::SqladminV1beta4::ListDatabasesResponse.new(items: []) }
let(:list_users_empty) { Google::Apis::SqladminV1beta4::ListUsersResponse.new(items: []) }
let(:list_databases) do
Google::Apis::SqladminV1beta4::ListDatabasesResponse.new(items: [
Google::Apis::SqladminV1beta4::Database.new(name: 'postgres'),
Google::Apis::SqladminV1beta4::Database.new(name: 'main_db')
])
end
let(:list_users) do
Google::Apis::SqladminV1beta4::ListUsersResponse.new(items: [
Google::Apis::SqladminV1beta4::User.new(name: 'postgres'),
Google::Apis::SqladminV1beta4::User.new(name: 'main_user')
])
end
context 'when unauthorized user triggers worker' do
subject do
@ -76,6 +91,8 @@ RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |google_api_client|
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).to receive(:create_cloudsql_database).and_return(operation_fail)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases_empty)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users_empty)
end
message = subject[:message]
@ -92,6 +109,8 @@ RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).to receive(:create_cloudsql_database).and_return(operation_done)
expect(google_api_client).to receive(:create_cloudsql_user).and_return(operation_fail)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases_empty)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users_empty)
end
message = subject[:message]
@ -102,12 +121,59 @@ RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do
end
end
context 'when database and user already exist' do
it 'does not try to create a database or user' do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |google_api_client|
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).not_to receive(:create_cloudsql_database)
expect(google_api_client).not_to receive(:create_cloudsql_user)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users)
end
status = subject[:status]
expect(status).to eq(:success)
end
end
context 'when database already exists' do
it 'does not try to create a database' do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |google_api_client|
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).not_to receive(:create_cloudsql_database)
expect(google_api_client).to receive(:create_cloudsql_user).and_return(operation_done)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users_empty)
end
status = subject[:status]
expect(status).to eq(:success)
end
end
context 'when user already exists' do
it 'does not try to create a user' do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |google_api_client|
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).to receive(:create_cloudsql_database).and_return(operation_done)
expect(google_api_client).not_to receive(:create_cloudsql_user)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases_empty)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users)
end
status = subject[:status]
expect(status).to eq(:success)
end
end
context 'when database and user creation succeeds' do
it 'stores project CI vars' do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |google_api_client|
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).to receive(:create_cloudsql_database).and_return(operation_done)
expect(google_api_client).to receive(:create_cloudsql_user).and_return(operation_done)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases_empty)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users_empty)
end
subject
@ -143,6 +209,8 @@ RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do
expect(google_api_client).to receive(:get_cloudsql_instance).and_return(get_instance_response_runnable)
expect(google_api_client).to receive(:create_cloudsql_database).and_return(operation_done)
expect(google_api_client).to receive(:create_cloudsql_user).and_return(operation_done)
expect(google_api_client).to receive(:list_cloudsql_databases).and_return(list_databases_empty)
expect(google_api_client).to receive(:list_cloudsql_users).and_return(list_users_empty)
end
subject