diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-29 21:08:53 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-29 21:08:53 +0000 |
| commit | a5c89610f3a29118fd7cb1de80ad1f8f0be5363e (patch) | |
| tree | 20befdc0eb6370b726faa83f91e8d52d14dfe099 | |
| parent | a16398e10f87edd229a12be2f1fc87cd4a76f902 (diff) | |
| download | gitlab-ce-a5c89610f3a29118fd7cb1de80ad1f8f0be5363e.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
71 files changed, 725 insertions, 202 deletions
diff --git a/.rubocop_todo/layout/space_inside_block_braces.yml b/.rubocop_todo/layout/space_inside_block_braces.yml index c084147eb58..f6128bda7f0 100644 --- a/.rubocop_todo/layout/space_inside_block_braces.yml +++ b/.rubocop_todo/layout/space_inside_block_braces.yml @@ -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' diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 74aed1bd984..92ca8654287 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -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) { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 03dfd0397b0..68fdfae6713 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -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; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 645f145328b..9692becef4f 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -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; - } } } diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fb38ba4f6c5..fd830536535 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -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 diff --git a/app/models/deployment.rb b/app/models/deployment.rb index c25ba6f9268..68aadf577a0 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -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 diff --git a/app/models/environment.rb b/app/models/environment.rb index 855fb281d81..02bdbd86cd1 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -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) diff --git a/app/services/deployments/update_environment_service.rb b/app/services/deployments/update_environment_service.rb index 9f2c9596fb4..3cacedc7d6e 100644 --- a/app/services/deployments/update_environment_service.rb +++ b/app/services/deployments/update_environment_service.rb @@ -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 diff --git a/app/services/google_cloud/base_service.rb b/app/services/google_cloud/base_service.rb index 016ab15408f..01aee2231c9 100644 --- a/app/services/google_cloud/base_service.rb +++ b/app/services/google_cloud/base_service.rb @@ -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) diff --git a/app/services/google_cloud/create_cloudsql_instance_service.rb b/app/services/google_cloud/create_cloudsql_instance_service.rb new file mode 100644 index 00000000000..f7fca277c52 --- /dev/null +++ b/app/services/google_cloud/create_cloudsql_instance_service.rb @@ -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 diff --git a/app/services/google_cloud/enable_cloudsql_service.rb b/app/services/google_cloud/enable_cloudsql_service.rb new file mode 100644 index 00000000000..a466b2f3696 --- /dev/null +++ b/app/services/google_cloud/enable_cloudsql_service.rb @@ -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 diff --git a/app/services/google_cloud/get_cloudsql_instances_service.rb b/app/services/google_cloud/get_cloudsql_instances_service.rb new file mode 100644 index 00000000000..701e83d556d --- /dev/null +++ b/app/services/google_cloud/get_cloudsql_instances_service.rb @@ -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 diff --git a/app/services/google_cloud/setup_cloudsql_instance_service.rb b/app/services/google_cloud/setup_cloudsql_instance_service.rb index 73650ee752f..10237f83b37 100644 --- a/app/services/google_cloud/setup_cloudsql_instance_service.rb +++ b/app/services/google_cloud/setup_cloudsql_instance_service.rb @@ -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}") - end + 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) - user_response = google_api_client.create_cloudsql_user(gcp_project_id, instance_name, username, password) + existing_database = list_database_response.items.find { |database| database.name == database_name } + existing_user = list_user_response.items.find { |user| user.name == username } - if user_response.status != OPERATION_STATE_DONE - return error("User creation failed: #{user_response.to_json}") + if existing_database && existing_user + save_database_ci_vars + save_user_ci_vars(existing_user) + return success end - primary_ip_address = get_instance_response.ip_addresses.first.ip_address - connection_name = get_instance_response.connection_name + database_response = execute_database_setup(existing_database) + return database_response if database_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_database_ci_vars + + user_response = execute_user_setup(existing_user) + return user_response if user_response[:status] == :error + + 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 diff --git a/app/views/projects/buttons/_remove_tag.html.haml b/app/views/projects/buttons/_remove_tag.html.haml index 060a854d4e4..dfa643a87bb 100644 --- a/app/views/projects/buttons/_remove_tag.html.haml +++ b/app/views/projects/buttons/_remove_tag.html.haml @@ -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 } }) diff --git a/app/views/projects/tags/_edit_release_button.html.haml b/app/views/projects/tags/_edit_release_button.html.haml index 05e0d0f0fa8..1c2626e5612 100644 --- a/app/views/projects/tags/_edit_release_button.html.haml +++ b/app/views/projects/tags/_edit_release_button.html.haml @@ -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') diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 258f662420b..f9605b814fa 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -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 diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 24da8e2db87..0a060310ae7 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -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' } } diff --git a/doc/.vale/gitlab/MultiLineLinks.yml b/doc/.vale/gitlab/MultiLineLinks.yml new file mode 100644 index 00000000000..bb0efc3a2de --- /dev/null +++ b/doc/.vale/gitlab/MultiLineLinks.yml @@ -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[^\)]*\)' diff --git a/doc/architecture/blueprints/ci_scale/index.md b/doc/architecture/blueprints/ci_scale/index.md index 1c3aee2f860..40318f19286 100644 --- a/doc/architecture/blueprints/ci_scale/index.md +++ b/doc/architecture/blueprints/ci_scale/index.md @@ -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. diff --git a/doc/architecture/blueprints/graphql_api/index.md b/doc/architecture/blueprints/graphql_api/index.md index b3ba1ad1960..2dfd83dc6e4 100644 --- a/doc/architecture/blueprints/graphql_api/index.md +++ b/doc/architecture/blueprints/graphql_api/index.md @@ -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 diff --git a/doc/development/database/avoiding_downtime_in_migrations.md b/doc/development/database/avoiding_downtime_in_migrations.md index 2d079656e23..bc264177324 100644 --- a/doc/development/database/avoiding_downtime_in_migrations.md +++ b/doc/development/database/avoiding_downtime_in_migrations.md @@ -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 diff --git a/doc/development/database/loose_foreign_keys.md b/doc/development/database/loose_foreign_keys.md index 52dc9c476c9..a390576c4e5 100644 --- a/doc/development/database/loose_foreign_keys.md +++ b/doc/development/database/loose_foreign_keys.md @@ -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 diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 48678dd32fa..72d489304c7 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -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 diff --git a/lib/gitlab/ci/jwt.rb b/lib/gitlab/ci/jwt.rb index c294291e538..d3e7210b820 100644 --- a/lib/gitlab/ci/jwt.rb +++ b/lib/gitlab/ci/jwt.rb @@ -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 diff --git a/lib/gitlab/ci/pipeline/seed/environment.rb b/lib/gitlab/ci/pipeline/seed/environment.rb index c8795840e5f..6bcc71a808b 100644 --- a/lib/gitlab/ci/pipeline/seed/environment.rb +++ b/lib/gitlab/ci/pipeline/seed/environment.rb @@ -30,7 +30,7 @@ module Gitlab end def deployment_tier - job.environment_deployment_tier + job.environment_tier_from_options end def expanded_environment_name diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index b1a69a5e61f..fb1b6657012 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -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) diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index eab41572cda..e500745c4f1 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -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) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb index 36b7378ee2a..206e6b8a456 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb @@ -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 diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb index 3373f4f4233..6ce4217f8ac 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb @@ -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| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb index 8074e1fa992..3db8128bc6d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb @@ -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 diff --git a/qa/qa/specs/features/sanity/feature_flags_spec.rb b/qa/qa/specs/features/sanity/feature_flags_spec.rb index f771978802e..acb9528fe6a 100644 --- a/qa/qa/specs/features/sanity/feature_flags_spec.rb +++ b/qa/qa/specs/features/sanity/feature_flags_spec.rb @@ -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 diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb index 659f66a67d2..a16adaf6076 100644 --- a/spec/features/admin/admin_mode/login_spec.rb +++ b/spec/features/admin/admin_mode/login_spec.rb @@ -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 diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index e5df6cc0fd3..236327ea687 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -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') diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb index 537b677cbd0..2e4dc4a29fc 100644 --- a/spec/features/boards/board_filters_spec.rb +++ b/spec/features/boards/board_filters_spec.rb @@ -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') diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb index 6a09e3c9506..7fa440befc1 100644 --- a/spec/features/boards/reload_boards_on_browser_back_spec.rb +++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb @@ -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 diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb index 1b349fa2276..d157d44bab7 100644 --- a/spec/features/dashboard/archived_projects_spec.rb +++ b/spec/features/dashboard/archived_projects_spec.rb @@ -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 diff --git a/spec/features/error_tracking/user_filters_errors_by_status_spec.rb b/spec/features/error_tracking/user_filters_errors_by_status_spec.rb index d5dbe259159..2ac43f67f64 100644 --- a/spec/features/error_tracking/user_filters_errors_by_status_spec.rb +++ b/spec/features/error_tracking/user_filters_errors_by_status_spec.rb @@ -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) diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index c86705832b1..eec07c84cde 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -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') } diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 70279e5067a..c93ed01b873 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -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 diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb index 04bf704b6a4..66ed6044de6 100644 --- a/spec/features/issuables/user_sees_sidebar_spec.rb +++ b/spec/features/issuables/user_sees_sidebar_spec.rb @@ -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) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 8732e2ecff2..fa4ce6fe1c1 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -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 diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index d63d21353e5..6a53c12eda3 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -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) diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb index 0533f1688e2..1ef2918adec 100644 --- a/spec/features/issues/user_bulk_edits_issues_spec.rb +++ b/spec/features/issues/user_bulk_edits_issues_spec.rb @@ -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) diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 892b57bac5c..c86a2c32e2d 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -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 diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index c6d743ed38f..d458c991668 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -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 diff --git a/spec/features/merge_request/user_approves_spec.rb b/spec/features/merge_request/user_approves_spec.rb index 4f7bcb58551..9670012803e 100644 --- a/spec/features/merge_request/user_approves_spec.rb +++ b/spec/features/merge_request/user_approves_spec.rb @@ -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 diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb index 059e1eb89c5..f0c0142a6cc 100644 --- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb +++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb @@ -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 } diff --git a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb index 92b9b785148..0dd87ac3e24 100644 --- a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb +++ b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb @@ -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 diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb index 7b7fff5c936..f56db3d3dbe 100644 --- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb +++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb @@ -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, diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index e045f11c0d8..c02149eed87 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -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 } diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 50f4cce5c23..2e65183d26f 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -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 = diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 09c6b6bce3b..2a1b9ea6009 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -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) diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index 2c2a2dfd4a8..0e86e970f46 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -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])) diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index b48659353ec..563120fc8b7 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -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 diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 4b6ed458c68..2f7b722f553 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -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 diff --git a/spec/features/projects/cluster_agents_spec.rb b/spec/features/projects/cluster_agents_spec.rb index 5d931afe4a7..8c557a9c37a 100644 --- a/spec/features/projects/cluster_agents_spec.rb +++ b/spec/features/projects/cluster_agents_spec.rb @@ -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 } diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 863fdbdadaa..2719316c5dc 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -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) diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 951b24eafac..a53e8beb555 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -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) } diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 53fdd5a15dd..0f3ce5a2bad 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -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") diff --git a/spec/features/projects/members/manage_groups_spec.rb b/spec/features/projects/members/manage_groups_spec.rb index 006fa3b6eff..e86affbbca1 100644 --- a/spec/features/projects/members/manage_groups_spec.rb +++ b/spec/features/projects/members/manage_groups_spec.rb @@ -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 diff --git a/spec/features/projects/pipelines/legacy_pipelines_spec.rb b/spec/features/projects/pipelines/legacy_pipelines_spec.rb index 15d889933bf..ca1610d5d35 100644 --- a/spec/features/projects/pipelines/legacy_pipelines_spec.rb +++ b/spec/features/projects/pipelines/legacy_pipelines_spec.rb @@ -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) } diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 785edc69623..d8de5e87a16 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -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) } diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb index 0df4bd3f0d9..86c5c3d2d8c 100644 --- a/spec/features/projects/settings/service_desk_setting_spec.rb +++ b/spec/features/projects/settings/service_desk_setting_spec.rb @@ -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) } diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb index 53e89cd2959..163e347d03d 100644 --- a/spec/features/projects/tree/tree_show_spec.rb +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -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) diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 259a82498b9..22fba3af1c9 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -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 diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index d8818d46836..aeca7b09a88 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -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 diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b317fd8833b..c1a740b103d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -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 } diff --git a/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb b/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb new file mode 100644 index 00000000000..cd0dd75e576 --- /dev/null +++ b/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb @@ -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 diff --git a/spec/services/google_cloud/enable_cloudsql_service_spec.rb b/spec/services/google_cloud/enable_cloudsql_service_spec.rb new file mode 100644 index 00000000000..e54e5a8d446 --- /dev/null +++ b/spec/services/google_cloud/enable_cloudsql_service_spec.rb @@ -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 diff --git a/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb b/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb new file mode 100644 index 00000000000..4587a5077c0 --- /dev/null +++ b/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb @@ -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 diff --git a/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb b/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb index 55553097423..e0a622bfa4a 100644 --- a/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb +++ b/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb @@ -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 |
