Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-01 00:07:40 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-01 00:07:40 +0300
commit9e83d078577a9c066f21fcef1355f800ad895c9c (patch)
tree8995c5868449584040e58400ae2bcb9f48346fb3
parent22dde36e800253350e5fa1d902f191a7f64bc6e9 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/style/empty_method.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock4
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/services/import_csv/base_service.rb99
-rw-r--r--app/services/issuable/import_csv/base_service.rb80
-rw-r--r--app/services/issues/import_csv_service.rb8
-rw-r--r--app/services/jira_connect_installations/update_service.rb2
-rw-r--r--app/views/search/_results_list.html.haml2
-rw-r--r--app/views/search/results/_error.html.haml7
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--config/feature_flags/development/gitlab_pat_auto_revocation.yml8
-rw-r--r--config/feature_flags/development/use_response_url_for_chat_responder.yml8
-rw-r--r--config/initializers/bootstrap_form.rb9
-rw-r--r--doc/api/graphql/reference/index.md19
-rw-r--r--doc/user/application_security/secret_detection/index.md58
-rw-r--r--doc/user/application_security/secret_detection/post_processing.md1
-rw-r--r--doc/user/infrastructure/iac/terraform_template_recipes.md63
-rw-r--r--doc/user/project/protected_branches.md8
-rw-r--r--lib/gitlab/chat/responder.rb17
-rw-r--r--lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/database/bulk_update.rb18
-rw-r--r--lib/gitlab/database/load_balancing/connection_proxy.rb6
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/initializers/0_postgresql_types_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder_spec.rb74
-rw-r--r--spec/services/import_csv/base_service_spec.rb64
-rw-r--r--spec/services/issues/import_csv_service_spec.rb2
-rw-r--r--spec/services/jira_connect_installations/update_service_spec.rb18
30 files changed, 440 insertions, 155 deletions
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index 763941fccf3..81b9544f574 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -108,7 +108,6 @@ Style/EmptyMethod:
- 'ee/app/controllers/registrations/company_controller.rb'
- 'ee/app/controllers/registrations/verification_controller.rb'
- 'ee/app/controllers/subscriptions/groups_controller.rb'
- - 'ee/app/controllers/trial_registrations_controller.rb'
- 'ee/app/controllers/trials_controller.rb'
- 'ee/app/controllers/users/identity_verification_controller.rb'
- 'ee/app/experiments/cart_abandonment_modal_experiment.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bd3e8d510ee..dafdd717c19 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-988e9e17374a0ae05a155ce0b459145c389c1524
+f6a9d44f05809aaa59fc9aff9b0465cf3cb7aea9
diff --git a/Gemfile b/Gemfile
index 196299e2f43..1ef6161b289 100644
--- a/Gemfile
+++ b/Gemfile
@@ -197,7 +197,6 @@ gem 'asciidoctor-plantuml', '~> 0.0.16'
gem 'asciidoctor-kroki', '~> 0.7.0', require: false
gem 'rouge', '~> 3.30.0'
gem 'truncato', '~> 0.7.12'
-gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.14.1'
# Calendar rendering
diff --git a/Gemfile.lock b/Gemfile.lock
index 993092a5e2e..f38f2a2871d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -246,9 +246,6 @@ GEM
binding_ninja (0.2.3)
bootsnap (1.16.0)
msgpack (~> 1.2)
- bootstrap_form (4.2.0)
- actionpack (>= 5.0)
- activemodel (>= 5.0)
browser (5.3.1)
builder (3.2.4)
bullet (7.0.2)
@@ -1609,7 +1606,6 @@ DEPENDENCIES
benchmark-memory (~> 0.1)
better_errors (~> 2.9.1)
bootsnap (~> 1.16.0)
- bootstrap_form (~> 4.2.0)
browser (~> 5.3.1)
bullet (~> 7.0.2)
bundler-audit (~> 0.7.0.1)
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 558cc360342..84182e57e2e 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -62,6 +62,8 @@ class SearchController < ApplicationController
@search_highlight = @search_service_presenter.search_highlight
end
+ return if @search_results.respond_to?(:failed?) && @search_results.failed?
+
Gitlab::Metrics::GlobalSearchSlis.record_apdex(
elapsed: @global_search_duration_s,
search_type: @search_type,
diff --git a/app/services/import_csv/base_service.rb b/app/services/import_csv/base_service.rb
new file mode 100644
index 00000000000..fd75a251dd2
--- /dev/null
+++ b/app/services/import_csv/base_service.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module ImportCsv
+ class BaseService
+ def initialize(user, project, csv_io)
+ @user = user
+ @project = project
+ @csv_io = csv_io
+ @results = { success: 0, error_lines: [], parse_error: false }
+ end
+
+ def execute
+ process_csv
+ email_results_to_user
+
+ results
+ end
+
+ def email_results_to_user
+ raise NotImplementedError
+ end
+
+ private
+
+ attr_reader :user, :project, :csv_io, :results
+
+ def attributes_for(row)
+ raise NotImplementedError
+ end
+
+ def validate_headers_presence!(headers)
+ raise NotImplementedError
+ end
+
+ def create_object_class
+ raise NotImplementedError
+ end
+
+ def process_csv
+ with_csv_lines.each do |row, line_no|
+ attributes = attributes_for(row)
+
+ if create_object(attributes)&.persisted?
+ results[:success] += 1
+ else
+ results[:error_lines].push(line_no)
+ end
+ end
+ rescue ArgumentError, CSV::MalformedCSVError
+ results[:parse_error] = true
+ end
+
+ def with_csv_lines
+ csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
+ validate_headers_presence!(csv_data.lines.first)
+
+ CSV.new(
+ csv_data,
+ col_sep: detect_col_sep(csv_data.lines.first),
+ headers: true,
+ header_converters: :symbol
+ ).each.with_index(2)
+ end
+
+ def detect_col_sep(header)
+ if header.include?(",")
+ ","
+ elsif header.include?(";")
+ ";"
+ elsif header.include?("\t")
+ "\t"
+ else
+ raise CSV::MalformedCSVError.new('Invalid CSV format', 1)
+ end
+ end
+
+ def create_object(attributes)
+ # NOTE: CSV imports are performed by workers, so we do not have a request context in order
+ # to create a SpamParams object to pass to the issuable create service.
+ spam_params = nil
+
+ # default_params can be extracted into a method if we need
+ # to support creation of objects that belongs to groups.
+ default_params = { project: project,
+ current_user: user,
+ params: attributes,
+ spam_params: spam_params }
+
+ create_service = create_object_class.new(**default_params.merge(extra_create_service_params))
+
+ create_service.execute_without_rate_limiting
+ end
+
+ # Overidden in subclasses to support specific parameters
+ def extra_create_service_params
+ {}
+ end
+ end
+end
diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb
index e84d1032e41..83cf5a67453 100644
--- a/app/services/issuable/import_csv/base_service.rb
+++ b/app/services/issuable/import_csv/base_service.rb
@@ -2,38 +2,13 @@
module Issuable
module ImportCsv
- class BaseService
- def initialize(user, project, csv_io)
- @user = user
- @project = project
- @csv_io = csv_io
- @results = { success: 0, error_lines: [], parse_error: false }
- end
-
- def execute
- process_csv
- email_results_to_user
-
- @results
- end
+ class BaseService < ::ImportCsv::BaseService
+ extend ::Gitlab::Utils::Override
private
- def process_csv
- with_csv_lines.each do |row, line_no|
- attributes = issuable_attributes_for(row)
-
- if create_issuable(attributes)&.persisted?
- @results[:success] += 1
- else
- @results[:error_lines].push(line_no)
- end
- end
- rescue ArgumentError, CSV::MalformedCSVError
- @results[:parse_error] = true
- end
-
- def issuable_attributes_for(row)
+ override :attributes_for
+ def attributes_for(row)
{
title: row[:title],
description: row[:description],
@@ -41,58 +16,13 @@ module Issuable
}
end
- def with_csv_lines
- csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
- validate_headers_presence!(csv_data.lines.first)
-
- CSV.new(
- csv_data,
- col_sep: detect_col_sep(csv_data.lines.first),
- headers: true,
- header_converters: :symbol
- ).each.with_index(2)
- end
-
+ override :validate_headers_presence!
def validate_headers_presence!(headers)
headers.downcase! if headers
return if headers && headers.include?('title') && headers.include?('description')
raise CSV::MalformedCSVError
end
-
- def detect_col_sep(header)
- if header.include?(",")
- ","
- elsif header.include?(";")
- ";"
- elsif header.include?("\t")
- "\t"
- else
- raise CSV::MalformedCSVError
- end
- end
-
- def create_issuable(attributes)
- # NOTE: CSV imports are performed by workers, so we do not have a request context in order
- # to create a SpamParams object to pass to the issuable create service.
- spam_params = nil
- create_service = create_issuable_class.new(project: @project, current_user: @user, params: attributes, spam_params: spam_params)
-
- # For now, if create_issuable_class prepends RateLimitedService let's bypass rate limiting
- if create_issuable_class < RateLimitedService
- create_service.execute_without_rate_limiting
- else
- create_service.execute
- end
- end
-
- def email_results_to_user
- # defined in ImportCsvService
- end
-
- def create_issuable_class
- # defined in ImportCsvService
- end
end
end
end
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
index 83e550583f6..c3d6af952b4 100644
--- a/app/services/issues/import_csv_service.rb
+++ b/app/services/issues/import_csv_service.rb
@@ -9,21 +9,21 @@ module Issues
end
def email_results_to_user
- Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_later
+ Notify.import_issues_csv_email(user.id, project.id, results).deliver_later
end
private
- def create_issuable(attributes)
+ def create_object(attributes)
super[:issue]
end
- def create_issuable_class
+ def create_object_class
Issues::CreateService
end
def record_import_attempt
- Issues::CsvImport.create!(user: @user, project: @project)
+ Issues::CsvImport.create!(user: user, project: project)
end
end
end
diff --git a/app/services/jira_connect_installations/update_service.rb b/app/services/jira_connect_installations/update_service.rb
index b2b6f2a91f2..ff5b9671e2b 100644
--- a/app/services/jira_connect_installations/update_service.rb
+++ b/app/services/jira_connect_installations/update_service.rb
@@ -24,7 +24,7 @@ module JiraConnectInstallations
end
end
- send_uninstalled_hook if instance_url_changed?
+ send_uninstalled_hook if instance_url_changed? && @installation.instance_url.blank?
ServiceResponse.new(status: :success)
end
diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml
index 195f0f3ad8a..7a57b5cc0fc 100644
--- a/app/views/search/_results_list.html.haml
+++ b/app/views/search/_results_list.html.haml
@@ -1,5 +1,7 @@
- if @timeout
= render partial: "search/results/timeout"
+- elsif @search_results.respond_to?(:failed?) && @search_results.failed?
+ = render partial: "search/results/error"
- elsif @search_objects.to_a.empty?
= render partial: "search/results/empty"
- else
diff --git a/app/views/search/results/_error.html.haml b/app/views/search/results/_error.html.haml
new file mode 100644
index 00000000000..f0d9283c620
--- /dev/null
+++ b/app/views/search/results/_error.html.haml
@@ -0,0 +1,7 @@
+.gl-display-flex.gl-flex-direction-column.gl-align-items-center
+ %div
+ .svg-content.svg-150
+ = image_tag 'illustrations/search-timeout-md.svg'
+ %div
+ %h4.gl-text-center.gl-font-weight-bold= s_('SearchError|A search query problem has occurred')
+ %p.gl-text-center= s_('SearchError|To resolve the problem, check the query syntax and try again.')
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 1727fabebd1..04103794e60 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -8,7 +8,7 @@
- group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name)
- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace)
-- if @search_results
+- if @search_results && !(@search_results.respond_to?(:failed?) && @search_results.failed?)
- if @search_service_presenter.without_count?
- page_description(_("%{scope} results for term '%{term}'") % { scope: @scope, term: @search_term })
- else
diff --git a/config/feature_flags/development/gitlab_pat_auto_revocation.yml b/config/feature_flags/development/gitlab_pat_auto_revocation.yml
deleted file mode 100644
index 3bbbadac23f..00000000000
--- a/config/feature_flags/development/gitlab_pat_auto_revocation.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: gitlab_pat_auto_revocation
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103713
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382610
-milestone: '15.6'
-type: development
-group: group::static analysis
-default_enabled: false
diff --git a/config/feature_flags/development/use_response_url_for_chat_responder.yml b/config/feature_flags/development/use_response_url_for_chat_responder.yml
new file mode 100644
index 00000000000..84ac2a27fab
--- /dev/null
+++ b/config/feature_flags/development/use_response_url_for_chat_responder.yml
@@ -0,0 +1,8 @@
+---
+name: use_response_url_for_chat_responder
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110625
+rollout_issue_url:
+milestone: '15.9'
+type: development
+group: group::integrations
+default_enabled: false
diff --git a/config/initializers/bootstrap_form.rb b/config/initializers/bootstrap_form.rb
deleted file mode 100644
index 8121bc8bf1d..00000000000
--- a/config/initializers/bootstrap_form.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module BootstrapFormBuilderCustomization
- def label_class
- "label-bold"
- end
-end
-
-BootstrapForm::FormBuilder.prepend(BootstrapFormBuilderCustomization)
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4eab45a67fb..a16a8c0e662 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5029,6 +5029,25 @@ Input type: `SecurityFindingDismissInput`
| <a id="mutationsecurityfindingdismisserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationsecurityfindingdismissuuid"></a>`uuid` | [`String`](#string) | UUID of dismissed finding. |
+### `Mutation.securityFindingRevertToDetected`
+
+Input type: `SecurityFindingRevertToDetectedInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationsecurityfindingreverttodetectedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationsecurityfindingreverttodetecteduuid"></a>`uuid` | [`String!`](#string) | UUID of the finding to be dismissed. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationsecurityfindingreverttodetectedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationsecurityfindingreverttodetectederrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationsecurityfindingreverttodetectedsecurityfinding"></a>`securityFinding` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | Finding reverted to detected. |
+
### `Mutation.securityPolicyProjectAssign`
Assigns the specified project(`security_policy_project_id`) as security policy project for the given project(`full_path`). If the project already has a security policy project, this reassigns the project's security policy project with the given `security_policy_project_id`.
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 016a5b42086..d316ed7a9de 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -65,6 +65,64 @@ Different features are available in different [GitLab tiers](https://about.gitla
| [Access the Security Dashboard](../security_dashboard/index.md) | **{dotted-circle}** No | **{check-circle}** Yes |
| [Customize Secret Detection rulesets](#custom-rulesets) | **{dotted-circle}** No | **{check-circle}** Yes |
+## Coverage
+
+Secret Detection scans different aspects of your code, depending on the situation. For all methods
+except "Default branch", Secret Detection scans commits, not the working tree. For example,
+Secret Detection can detect if a secret was added in one commit and removed in a later commit.
+
+- Historical scan
+
+ If the `SECRET_DETECTION_HISTORIC_SCAN` variable is set, the content of all
+ [branches](../../project/repository/branches/index.md) is scanned. Before scanning the
+ repository's content, Secret Detection runs the command `git fetch --all` to fetch the content of all
+ branches.
+
+- Commit range
+
+ If the `SECRET_DETECTION_LOG_OPTS` variable is set, the secrets analyzer fetches the entire
+ history of the branch or reference the pipeline is being run for. Secret Detection then runs,
+ scanning the commit range specified.
+
+- Default branch
+
+ When Secret Detection is run on the default branch, the Git repository is treated as a plain
+ folder. Only the contents of the repository at the current HEAD are scanned. Commit history is not scanned.
+
+- Push event
+
+ On a push event, Secret Detection determines what commit range to scan, given the information
+ available in the runner. To determine the commit range, the variables `CI_COMMIT_SHA` and
+ `CI_COMMIT_BEFORE_SHA` are important.
+
+ - `CI_COMMIT_SHA` is the commit at HEAD for a given branch. This variable is always set for push events.
+ - `CI_COMMIT_BEFORE_SHA` is set in most cases. However, it is not set for the first push event on
+ a new branch, nor for merge pipelines. Because of this, Secret Detection can't be guaranteed
+ when multiple commits are committed to a new branch.
+
+- Merge request
+
+ In a merge request, Secret Detection scans every commit made on the source branch. To use this
+ feature, you must use the [`latest` Secret Detection template](#templates), as it supports
+ [merge request pipelines](../../../ci/pipelines/merge_request_pipelines.md).
+
+## Templates
+
+Secret Detection default configuration is defined in CI/CD templates. Updates to the template are
+provided with GitLab upgrades, allowing you to benefit from any improvements and additions.
+
+Available templates:
+
+- [`Secret-Detection.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml): Stable version of the Secret Detection CI/CD template.
+- [`Secret-Detection.latest.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml): Latest version of the Secret Detection template.
+
+WARNING:
+The latest version of the template may include breaking changes. Use the stable template unless you
+need a feature provided only in the latest template.
+
+For more information about template versioning, see the
+[CI/CD documentation](../../../development/cicd/templates.md#latest-version).
+
## Enable Secret Detection
Prerequisites:
diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md
index f918c145552..6f74a10f479 100644
--- a/doc/user/application_security/secret_detection/post_processing.md
+++ b/doc/user/application_security/secret_detection/post_processing.md
@@ -8,6 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
> - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only.
+> - [Enabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.9
FLAG:
By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com
diff --git a/doc/user/infrastructure/iac/terraform_template_recipes.md b/doc/user/infrastructure/iac/terraform_template_recipes.md
index 894c53c0f46..89a97f305e4 100644
--- a/doc/user/infrastructure/iac/terraform_template_recipes.md
+++ b/doc/user/infrastructure/iac/terraform_template_recipes.md
@@ -29,6 +29,69 @@ The `destroy` job is part of the `cleanup` stage. Like the `deploy`
job, the `destroy` job is always `manual` and is not tied to the
default branch.
+To connect the `destroy` job to the GitLab environment:
+
+```yaml
+include:
+ - template: Terraform.latest.gitlab-ci.yml
+
+deploy:
+ envrionment:
+ name: $TF_STATE_NAME
+ action: start
+ on_stop: destroy
+
+destroy:
+ extends: .terraform:destroy
+ environment:
+ name: $TF_STATE_NAME
+ action: stop
+```
+
+In this configuration, the `destroy` job is always created. However, you might want to create a `destroy` job only if certain
+conditions are met.
+
+The following configuration creates a `destroy` job, runs a destroy plan and omits the `deploy` job only if `TF_DESTROY` is true:
+
+```yaml
+include:
+ - template: Terraform.latest.gitlab-ci.yml
+
+build:
+ rules:
+ - if: $TF_DESTROY == "true"
+ variables:
+ TF_CLI_ARGS_plan: "-destroy"
+ - when: on_success
+
+deploy:
+ envrionment:
+ name: $TF_STATE_NAME
+ action: start
+ on_stop: destroy
+ rules:
+ - if: $TF_DESTROY == "true"
+ when: never
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_AUTO_DEPLOY == "true"
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+ when: manual
+
+destroy:
+ extends: .terraform:destroy
+ dependencies:
+ - build
+ variables:
+ TF_CLI_ARGS_destroy: "${TF_PLAN_CACHE}"
+ environment:
+ name: $TF_STATE_NAME
+ action: stop
+ rules:
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == "true"
+ when: manual
+```
+
+This configuration has a known issue: when the `destroy` job is not in the same pipeline as the `deploy` job, the `on_stop` environment action does not work.
+
## Run a custom `terraform` command in a job
To define a job that runs a custom `terraform` command, the
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index b0754e74314..da53fe2f5e9 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -10,6 +10,14 @@ In GitLab, [permissions](../permissions.md) are fundamentally defined around the
idea of having read or write permission to the repository and branches. To impose
further restrictions on certain branches, they can be protected.
+A protected branch controls:
+
+- Which users can merge into the branch.
+- Which users can push to the branch.
+- If users can force push to the branch.
+- If changes to files listed in the CODEOWNERS file can be pushed directly to the branch.
+- Which users can unprotect the branch.
+
The [default branch](repository/branches/default.md) for your repository is protected by default.
## Who can modify a protected branch
diff --git a/lib/gitlab/chat/responder.rb b/lib/gitlab/chat/responder.rb
index 53a625e9d43..478be5bd350 100644
--- a/lib/gitlab/chat/responder.rb
+++ b/lib/gitlab/chat/responder.rb
@@ -11,10 +11,21 @@ module Gitlab
#
# build - A `Ci::Build` that executed a chat command.
def self.responder_for(build)
- integration = build.pipeline.chat_data&.chat_name&.integration
+ if Feature.enabled?(:use_response_url_for_chat_responder)
+ response_url = build.pipeline.chat_data&.response_url
+ return unless response_url
- if (responder = integration.try(:chat_responder))
- responder.new(build)
+ if response_url.start_with?('https://hooks.slack.com/')
+ Gitlab::Chat::Responder::Slack.new(build)
+ else
+ Gitlab::Chat::Responder::Mattermost.new(build)
+ end
+ else
+ integration = build.pipeline.chat_data&.chat_name&.integration
+
+ if (responder = integration.try(:chat_responder))
+ responder.new(build)
+ end
end
end
end
diff --git a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml
index 0b6c10293fc..f3f736e96c4 100644
--- a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml
@@ -24,6 +24,9 @@ validate:
build:
extends: .terraform:build
+ environment:
+ name: $TF_STATE_NAME
+ action: prepare
deploy:
extends: .terraform:deploy
@@ -31,3 +34,4 @@ deploy:
- build
environment:
name: $TF_STATE_NAME
+ action: start
diff --git a/lib/gitlab/database/bulk_update.rb b/lib/gitlab/database/bulk_update.rb
index 4b4a9b38fd8..36dbb157b0d 100644
--- a/lib/gitlab/database/bulk_update.rb
+++ b/lib/gitlab/database/bulk_update.rb
@@ -43,15 +43,7 @@ module Gitlab
end
def update!
- if without_prepared_statement?
- # A workaround for https://github.com/rails/rails/issues/24893
- # When prepared statements are prevented (such as when using the
- # query counter or in omnibus by default), we cannot call
- # `exec_update`, since that will discard the bindings.
- connection.send(:exec_no_cache, sql, log_name, params) # rubocop: disable GitlabSecurity/PublicSend
- else
- connection.exec_update(sql, log_name, params)
- end
+ connection.exec_update(sql, log_name, params)
end
def self.column_definitions(model, columns)
@@ -93,14 +85,6 @@ module Gitlab
end
end
- # A workaround for https://github.com/rails/rails/issues/24893
- # We need to detect if prepared statements have been disabled.
- def without_prepared_statement?
- strong_memoize(:without_prepared_statement) do
- connection.send(:without_prepared_statement?, [1]) # rubocop: disable GitlabSecurity/PublicSend
- end
- end
-
def query_attribute(column, key, values)
value = values[column.name]
key[column.name] = value if key.try(:id) # optimistic update
diff --git a/lib/gitlab/database/load_balancing/connection_proxy.rb b/lib/gitlab/database/load_balancing/connection_proxy.rb
index f0343f9d8b5..622e310ead3 100644
--- a/lib/gitlab/database/load_balancing/connection_proxy.rb
+++ b/lib/gitlab/database/load_balancing/connection_proxy.rb
@@ -97,11 +97,11 @@ module Gitlab
if current_session.use_primary? &&
!current_session.use_replicas_for_read_queries?
@load_balancer.read_write do |connection|
- connection.send(...)
+ connection.public_send(...)
end
else
@load_balancer.read do |connection|
- connection.send(...)
+ connection.public_send(...)
end
end
end
@@ -117,7 +117,7 @@ module Gitlab
end
@load_balancer.read_write do |connection|
- connection.send(...)
+ connection.public_send(...)
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 590bebb49bd..6f4459ccc3a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -37414,6 +37414,12 @@ msgstr ""
msgid "SearchCodeResults|of %{link_to_project}"
msgstr ""
+msgid "SearchError|A search query problem has occurred"
+msgstr ""
+
+msgid "SearchError|To resolve the problem, check the query syntax and try again."
+msgstr ""
+
msgid "SearchResults|Showing %{count} %{scope} for %{term_element}"
msgstr ""
diff --git a/spec/initializers/0_postgresql_types_spec.rb b/spec/initializers/0_postgresql_types_spec.rb
index 76b243033d0..99f9b76a34e 100644
--- a/spec/initializers/0_postgresql_types_spec.rb
+++ b/spec/initializers/0_postgresql_types_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'PostgreSQL registered types' do
- subject(:types) { ApplicationRecord.connection.send(:type_map).keys }
+ subject(:types) { ApplicationRecord.connection.reload_type_map.keys }
# These can be obtained via SELECT oid, typname from pg_type
it 'includes custom and standard OIDs' do
diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb
index 803f30da9e7..a9d290cb87c 100644
--- a/spec/lib/gitlab/chat/responder_spec.rb
+++ b/spec/lib/gitlab/chat/responder_spec.rb
@@ -2,30 +2,70 @@
require 'spec_helper'
-RSpec.describe Gitlab::Chat::Responder do
+RSpec.describe Gitlab::Chat::Responder, feature_category: :integrations do
describe '.responder_for' do
- context 'using a regular build' do
- it 'returns nil' do
- build = create(:ci_build)
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(use_response_url_for_chat_responder: false)
+ end
+
+ context 'using a regular build' do
+ it 'returns nil' do
+ build = create(:ci_build)
+
+ expect(described_class.responder_for(build)).to be_nil
+ end
+ end
+
+ context 'using a chat build' do
+ it 'returns the responder for the build' do
+ pipeline = create(:ci_pipeline)
+ build = create(:ci_build, pipeline: pipeline)
+ integration = double(:integration, chat_responder: Gitlab::Chat::Responder::Slack)
+ chat_name = double(:chat_name, integration: integration)
+ chat_data = double(:chat_data, chat_name: chat_name)
+
+ allow(pipeline)
+ .to receive(:chat_data)
+ .and_return(chat_data)
- expect(described_class.responder_for(build)).to be_nil
+ expect(described_class.responder_for(build))
+ .to be_an_instance_of(Gitlab::Chat::Responder::Slack)
+ end
end
end
- context 'using a chat build' do
- it 'returns the responder for the build' do
- pipeline = create(:ci_pipeline)
- build = create(:ci_build, pipeline: pipeline)
- integration = double(:integration, chat_responder: Gitlab::Chat::Responder::Slack)
- chat_name = double(:chat_name, integration: integration)
- chat_data = double(:chat_data, chat_name: chat_name)
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(use_response_url_for_chat_responder: true)
+ end
+
+ context 'using a regular build' do
+ it 'returns nil' do
+ build = create(:ci_build)
+
+ expect(described_class.responder_for(build)).to be_nil
+ end
+ end
+
+ context 'using a chat build' do
+ let(:chat_name) { create(:chat_name, chat_id: 'U123') }
+ let(:pipeline) do
+ pipeline = create(:ci_pipeline)
+ pipeline.create_chat_data!(
+ response_url: 'https://hooks.slack.com/services/12345',
+ chat_name_id: chat_name.id
+ )
+ pipeline
+ end
- allow(pipeline)
- .to receive(:chat_data)
- .and_return(chat_data)
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:responder) { described_class.new(build) }
- expect(described_class.responder_for(build))
- .to be_an_instance_of(Gitlab::Chat::Responder::Slack)
+ it 'returns the responder for the build' do
+ expect(described_class.responder_for(build))
+ .to be_an_instance_of(Gitlab::Chat::Responder::Slack)
+ end
end
end
end
diff --git a/spec/services/import_csv/base_service_spec.rb b/spec/services/import_csv/base_service_spec.rb
new file mode 100644
index 00000000000..0c0ed40ff4d
--- /dev/null
+++ b/spec/services/import_csv/base_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ImportCsv::BaseService, feature_category: :importers do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:csv_io) { double }
+
+ subject { described_class.new(user, project, csv_io) }
+
+ shared_examples 'abstract method' do |method, args|
+ it "raises NotImplemented error when #{method} is called" do
+ if args
+ expect { subject.send(method, args) }.to raise_error(NotImplementedError)
+ else
+ expect { subject.send(method) }.to raise_error(NotImplementedError)
+ end
+ end
+ end
+
+ it_behaves_like 'abstract method', :email_results_to_user
+ it_behaves_like 'abstract method', :attributes_for, "any"
+ it_behaves_like 'abstract method', :validate_headers_presence!, "any"
+ it_behaves_like 'abstract method', :create_object_class
+
+ describe '#detect_col_sep' do
+ context 'when header contains invalid separators' do
+ it 'raises error' do
+ header = 'Name&email'
+
+ expect { subject.send(:detect_col_sep, header) }.to raise_error(CSV::MalformedCSVError)
+ end
+ end
+
+ context 'when header is valid' do
+ shared_examples 'header with valid separators' do
+ let(:header) { "Name#{separator}email" }
+
+ it 'returns separator value' do
+ expect(subject.send(:detect_col_sep, header)).to eq(separator)
+ end
+ end
+
+ context 'with ; as separator' do
+ let(:separator) { ';' }
+
+ it_behaves_like 'header with valid separators'
+ end
+
+ context 'with \t as separator' do
+ let(:separator) { "\t" }
+
+ it_behaves_like 'header with valid separators'
+ end
+
+ context 'with , as separator' do
+ let(:separator) { ',' }
+
+ it_behaves_like 'header with valid separators'
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
index 9ad1d7dba9f..90e360f9cf1 100644
--- a/spec/services/issues/import_csv_service_spec.rb
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Issues::ImportCsvService do
+RSpec.describe Issues::ImportCsvService, feature_category: :team_planning do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:assignee) { create(:user, username: 'csv_assignee') }
diff --git a/spec/services/jira_connect_installations/update_service_spec.rb b/spec/services/jira_connect_installations/update_service_spec.rb
index ec5bb5d6d6a..15f3b485b20 100644
--- a/spec/services/jira_connect_installations/update_service_spec.rb
+++ b/spec/services/jira_connect_installations/update_service_spec.rb
@@ -45,8 +45,9 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ
let_it_be_with_reload(:installation) { create(:jira_connect_installation, instance_url: 'https://other_gitlab.example.com') }
it 'sends an installed event to the instance', :aggregate_failures do
- expect_next_instance_of(JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed,
-'https://other_gitlab.example.com') do |proxy_lifecycle_events_service|
+ expect_next_instance_of(
+ JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, 'https://other_gitlab.example.com'
+ ) do |proxy_lifecycle_events_service|
expect(proxy_lifecycle_events_service).to receive(:execute).and_return(ServiceResponse.new(status: :success))
end
@@ -62,19 +63,19 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ
stub_request(:post, 'https://other_gitlab.example.com/-/jira_connect/events/uninstalled')
end
- it 'starts an async worker to send an uninstalled event to the previous instance' do
- expect(JiraConnect::SendUninstalledHookWorker).to receive(:perform_async).with(installation.id, 'https://other_gitlab.example.com')
-
+ it 'sends an installed event to the instance and updates instance_url' do
expect(JiraConnectInstallations::ProxyLifecycleEventService)
.to receive(:execute).with(installation, :installed, 'https://gitlab.example.com')
.and_return(ServiceResponse.new(status: :success))
+ expect(JiraConnect::SendUninstalledHookWorker).not_to receive(:perform_async)
+
execute_service
expect(installation.instance_url).to eq(update_params[:instance_url])
end
- context 'and the new instance_url is empty' do
+ context 'and the new instance_url is nil' do
let(:update_params) { { instance_url: nil } }
it 'starts an async worker to send an uninstalled event to the previous instance' do
@@ -98,8 +99,9 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ
let(:update_params) { { instance_url: 'https://gitlab.example.com' } }
it 'sends an installed event to the instance and updates instance_url' do
- expect_next_instance_of(JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed,
-'https://gitlab.example.com') do |proxy_lifecycle_events_service|
+ expect_next_instance_of(
+ JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, 'https://gitlab.example.com'
+ ) do |proxy_lifecycle_events_service|
expect(proxy_lifecycle_events_service).to receive(:execute).and_return(ServiceResponse.new(status: :success))
end