summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-03 18:08:16 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-03 18:08:16 +0000
commite9c2bf267862e22c0770cc7b3a1ed97a8b87a7fd (patch)
tree7b778e44f210132af1233ceb8801b388ac3519f5
parent946771d0b016ae92b15a60bc3290a33b94191ffe (diff)
downloadgitlab-ce-e9c2bf267862e22c0770cc7b3a1ed97a8b87a7fd.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/lib/graphql.js4
-rw-r--r--app/controllers/projects/forks_controller.rb2
-rw-r--r--app/finders/fork_targets_finder.rb2
-rw-r--r--app/models/ci/bridge.rb4
-rw-r--r--app/models/ci/pipeline.rb7
-rw-r--r--app/models/snippet.rb8
-rw-r--r--app/services/ci/create_cross_project_pipeline_service.rb30
-rw-r--r--changelogs/unreleased/208167-bigfix-unable-to-fork-project-to-the-same-namespace.yml5
-rw-r--r--changelogs/unreleased/37320-ensure-project-snippet-api-status.yml5
-rw-r--r--changelogs/unreleased/confapi-repo-push-mirror.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-creation-race-conditions.yml5
-rw-r--r--changelogs/unreleased/groupapi-avatar-support.yml5
-rw-r--r--changelogs/unreleased/rs-commit-web_url.yml5
-rw-r--r--changelogs/unreleased/vh-snippets-content-types.yml5
-rw-r--r--changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml5
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/administration/gitaly/img/praefect_architecture_v12_9.pngbin0 -> 158447 bytes
-rw-r--r--doc/administration/gitaly/praefect.md756
-rw-r--r--doc/api/commits.md18
-rw-r--r--doc/api/groups.md2
-rw-r--r--lib/api/entities/commit.rb4
-rw-r--r--lib/api/helpers/groups_helpers.rb2
-rw-r--r--lib/api/project_snippets.rb5
-rw-r--r--lib/api/remote_mirrors.rb20
-rw-r--r--lib/gitlab/url_builder.rb5
-rw-r--r--package.json2
-rw-r--r--spec/finders/fork_targets_finder_spec.rb4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit/basic.json6
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb14
-rw-r--r--spec/models/ci/pipeline_spec.rb36
-rw-r--r--spec/requests/api/groups_spec.rb60
-rw-r--r--spec/requests/api/merge_requests_spec.rb20
-rw-r--r--spec/requests/api/project_snippets_spec.rb54
-rw-r--r--spec/requests/api/projects_spec.rb20
-rw-r--r--spec/requests/api/remote_mirrors_spec.rb48
-rw-r--r--spec/services/ci/create_cross_project_pipeline_service_spec.rb72
-rw-r--r--spec/services/git/branch_push_service_spec.rb15
-rw-r--r--spec/services/merge_requests/create_service_spec.rb23
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb56
-rw-r--r--yarn.lock8
40 files changed, 1001 insertions, 348 deletions
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index b49fe9362c2..8d3b87d5cc0 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -26,6 +26,10 @@ export default (resolvers = {}, config = {}) => {
headers: {
[csrf.headerKey]: csrf.token,
},
+ // fetch won’t send cookies in older browsers, unless you set the credentials init option.
+ // We set to `same-origin` which is default value in modern browsers.
+ // See https://github.com/whatwg/fetch/pull/585 for more information.
+ credentials: 'same-origin',
};
return new ApolloClient({
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index e0e8fb177ba..248b75d16ed 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -39,7 +39,7 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def new
- @namespaces = fork_service.valid_fork_targets
+ @namespaces = fork_service.valid_fork_targets - [project.namespace]
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/finders/fork_targets_finder.rb b/app/finders/fork_targets_finder.rb
index 9003c593757..7a08273fa0d 100644
--- a/app/finders/fork_targets_finder.rb
+++ b/app/finders/fork_targets_finder.rb
@@ -8,7 +8,7 @@ class ForkTargetsFinder
# rubocop: disable CodeReuse/ActiveRecord
def execute
- ::Namespace.where(id: user.manageable_namespaces).where.not(id: project.namespace).sort_by_type
+ ::Namespace.where(id: user.manageable_namespaces).sort_by_type
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 39be26abc1d..a28e44d44c1 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -62,6 +62,10 @@ module Ci
end
end
+ def has_downstream_pipeline?
+ sourced_pipelines.exists?
+ end
+
def downstream_pipeline_params
return child_params if triggers_child_pipeline?
return cross_project_params if downstream_project.present?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index e07abc20dcf..ff257c6cd68 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -227,6 +227,7 @@ module Ci
end
after_transition created: :pending do |pipeline|
+ next if Feature.enabled?(:ci_drop_bridge_on_downstream_errors, pipeline.project, default_enabled: true)
next unless pipeline.bridge_triggered?
next if pipeline.bridge_waiting?
@@ -756,6 +757,8 @@ module Ci
raise BridgeStatusError unless source_bridge.active?
source_bridge.success!
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e, pipeline_id: id)
end
def bridge_triggered?
@@ -774,6 +777,10 @@ module Ci
child_pipelines.exists?
end
+ def created_successfully?
+ persisted? && failure_reason.blank?
+ end
+
def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 233834dbaf9..a927235317c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -188,14 +188,6 @@ class Snippet < ApplicationRecord
end
end
- def self.content_types
- [
- ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
- ".haml", ".html", ".sass", ".scss", ".xml", ".php", ".erb",
- ".js", ".sh", ".coffee", ".yml", ".md"
- ]
- end
-
def blob
@blob ||= Blob.decorate(SnippetBlob.new(self), self)
end
diff --git a/app/services/ci/create_cross_project_pipeline_service.rb b/app/services/ci/create_cross_project_pipeline_service.rb
index 22b8e37a7e8..99f232bc892 100644
--- a/app/services/ci/create_cross_project_pipeline_service.rb
+++ b/app/services/ci/create_cross_project_pipeline_service.rb
@@ -5,9 +5,19 @@ module Ci
class CreateCrossProjectPipelineService < ::BaseService
include Gitlab::Utils::StrongMemoize
+ DuplicateDownstreamPipelineError = Class.new(StandardError)
+
def execute(bridge)
@bridge = bridge
+ if bridge.has_downstream_pipeline?
+ Gitlab::ErrorTracking.track_exception(
+ DuplicateDownstreamPipelineError.new,
+ bridge_id: @bridge.id, project_id: @bridge.project_id
+ )
+ return
+ end
+
pipeline_params = @bridge.downstream_pipeline_params
target_ref = pipeline_params.dig(:target_revision, :ref)
@@ -18,14 +28,32 @@ module Ci
current_user,
pipeline_params.fetch(:target_revision))
- service.execute(
+ downstream_pipeline = service.execute(
pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline|
pipeline.variables.build(@bridge.downstream_variables)
end
+
+ downstream_pipeline.tap do |pipeline|
+ next if Feature.disabled?(:ci_drop_bridge_on_downstream_errors, project, default_enabled: true)
+
+ update_bridge_status!(@bridge, pipeline)
+ end
end
private
+ def update_bridge_status!(bridge, pipeline)
+ Gitlab::OptimisticLocking.retry_lock(bridge) do |subject|
+ if pipeline.created_successfully?
+ # If bridge uses `strategy:depend` we leave it running
+ # and update the status when the downstream pipeline completes.
+ subject.success! unless subject.dependent?
+ else
+ subject.drop!(:downstream_pipeline_creation_failed)
+ end
+ end
+ end
+
def ensure_preconditions!(target_ref)
unless downstream_project_accessible?
@bridge.drop!(:downstream_bridge_project_not_found)
diff --git a/changelogs/unreleased/208167-bigfix-unable-to-fork-project-to-the-same-namespace.yml b/changelogs/unreleased/208167-bigfix-unable-to-fork-project-to-the-same-namespace.yml
new file mode 100644
index 00000000000..8685c8ca1f3
--- /dev/null
+++ b/changelogs/unreleased/208167-bigfix-unable-to-fork-project-to-the-same-namespace.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to fork to the same namespace and different path via API call
+merge_request: 26062
+author:
+type: fixed
diff --git a/changelogs/unreleased/37320-ensure-project-snippet-api-status.yml b/changelogs/unreleased/37320-ensure-project-snippet-api-status.yml
new file mode 100644
index 00000000000..e727bd726a9
--- /dev/null
+++ b/changelogs/unreleased/37320-ensure-project-snippet-api-status.yml
@@ -0,0 +1,5 @@
+---
+title: Project Snippets API endpoints check feature status
+merge_request: 26064
+author:
+type: performance
diff --git a/changelogs/unreleased/confapi-repo-push-mirror.yml b/changelogs/unreleased/confapi-repo-push-mirror.yml
new file mode 100644
index 00000000000..2fb52ed31d9
--- /dev/null
+++ b/changelogs/unreleased/confapi-repo-push-mirror.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for configuring remote mirrors via API
+merge_request: 25825
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/fix-pipeline-creation-race-conditions.yml b/changelogs/unreleased/fix-pipeline-creation-race-conditions.yml
new file mode 100644
index 00000000000..283e1541fb9
--- /dev/null
+++ b/changelogs/unreleased/fix-pipeline-creation-race-conditions.yml
@@ -0,0 +1,5 @@
+---
+title: Drop bridge if downstream pipeline has errors
+merge_request: 25706
+author:
+type: fixed
diff --git a/changelogs/unreleased/groupapi-avatar-support.yml b/changelogs/unreleased/groupapi-avatar-support.yml
new file mode 100644
index 00000000000..8219240f64e
--- /dev/null
+++ b/changelogs/unreleased/groupapi-avatar-support.yml
@@ -0,0 +1,5 @@
+---
+title: Add avatar upload support for create and update group APIs
+merge_request: 25751
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/rs-commit-web_url.yml b/changelogs/unreleased/rs-commit-web_url.yml
new file mode 100644
index 00000000000..c9d521660e8
--- /dev/null
+++ b/changelogs/unreleased/rs-commit-web_url.yml
@@ -0,0 +1,5 @@
+---
+title: Add web_url attribute to API response for Commits
+merge_request: 26173
+author:
+type: added
diff --git a/changelogs/unreleased/vh-snippets-content-types.yml b/changelogs/unreleased/vh-snippets-content-types.yml
new file mode 100644
index 00000000000..f6589abb522
--- /dev/null
+++ b/changelogs/unreleased/vh-snippets-content-types.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused Snippets#content_types method
+merge_request: 26306
+author:
+type: other
diff --git a/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml b/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml
new file mode 100644
index 00000000000..1863f0de26f
--- /dev/null
+++ b/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml
@@ -0,0 +1,5 @@
+---
+title: Send credentials with GraphQL fetch requests
+merge_request: 26386
+author:
+type: fixed
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 2f4079396e6..5b3052789e4 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -79,6 +79,8 @@ From there, you can see the following actions:
- Release was added to a project
- Release was updated
- Release milestone associations changed
+- Permission to approve merge requests by committers was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/7531) in GitLab 12.9)
+- Permission to approve merge requests by authors was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/7531) in GitLab 12.9)
### Instance events **(PREMIUM ONLY)**
diff --git a/doc/administration/gitaly/img/praefect_architecture_v12_9.png b/doc/administration/gitaly/img/praefect_architecture_v12_9.png
new file mode 100644
index 00000000000..3937789094c
--- /dev/null
+++ b/doc/administration/gitaly/img/praefect_architecture_v12_9.png
Binary files differ
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 7f314bc5e31..20c57683708 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -1,359 +1,525 @@
-# Praefect
+# Praefect: High Availability
-NOTE: **Note:** Praefect is an experimental service, and for testing purposes only at
-this time.
+NOTE: **Note:** Praefect is an experimental service, and data loss is likely.
Praefect is an optional reverse-proxy for [Gitaly](../index.md) to manage a
-cluster of Gitaly nodes for high availability through replication.
-If a Gitaly node becomes unavailable, it will be possible to fail over to a
-warm Gitaly replica.
+cluster of Gitaly nodes for high availability. Initially, high availability
+be implemented through asynchronous replication. If a Gitaly node becomes
+unavailable, it will be possible to fail over to a warm Gitaly replica.
The first minimal version will support:
- Eventual consistency of the secondary replicas.
-- Manual fail over from the primary to the secondary.
+- Automatic fail over from the primary to the secondary.
+- Reporting of possible data loss if replication queue is non empty.
Follow the [HA Gitaly epic](https://gitlab.com/groups/gitlab-org/-/epics/1489)
for updates and roadmap.
-## Omnibus
+## Requirements for configuring Gitaly for High Availability
-### Architecture
+NOTE: **Note:** this reference architecture is not highly available because
+Praefect is a single point of failure.
-The most common architecture for Praefect is simplified in the diagram below:
+The minimal [alpha](https://about.gitlab.com/handbook/product/#alpha-beta-ga)
+reference architecture additionally requires:
-```mermaid
-graph TB
- GitLab --> Praefect;
- Praefect --- PostgreSQL;
- Praefect --> Gitaly1;
- Praefect --> Gitaly2;
- Praefect --> Gitaly3;
-```
+- 1 Praefect node
+- 1 PostgreSQL server (PostgreSQL 9.6 or newer)
+- 3 Gitaly nodes (1 primary, 2 secondary)
+
+![Alpha architecture diagram](img/praefect_architecture_v12_9.png)
+
+See the [design
+document](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/design_ha.md)
+for implementation details.
+
+## Setup Instructions
-Where `GitLab` is the collection of clients that can request Git operations.
-The Praefect node has three storage nodes attached. Praefect itself doesn't
-store data, but connects to three Gitaly nodes, `Gitaly-1`, `Gitaly-2`, and `Gitaly-3`.
+If you [installed](https://about.gitlab.com/install/) GitLab using the Omnibus
+package (highly recommended), follow the steps below:
-In order to keep track of replication state, Praefect relies on a
-PostgreSQL database. This database is a single point of failure so you
-should use a highly available PostgreSQL server for this. GitLab
-itself needs a HA PostgreSQL server too, so you could optionally co-locate the Praefect
-SQL database on the PostgreSQL server you use for the rest of GitLab.
+1. [Preparation](#preparation)
+1. [Configuring the Praefect database](#postgresql)
+1. [Configuring the Praefect proxy/router](#praefect)
+1. [Configuring each Gitaly node](#gitaly) (once for each Gitaly node)
+1. [Updating the GitLab server configuration](#gitlab)
-Praefect may be enabled on its own node or can be run on the GitLab server.
-In the example below we will use a separate server, but the optimal configuration
-for Praefect is still being determined.
+### Preparation
-Praefect will handle all Gitaly RPC requests to its child nodes. However, the child nodes
-will still need to communicate with the GitLab server via its internal API for authentication
-purposes.
+Before beginning, you should already have a working GitLab instance. [Learn how
+to install GitLab](https://about.gitlab.com/install/).
-### Setup
+Provision a PostgreSQL server (PostgreSQL 9.6 or newer). Configuration through
+the GitLab Omnibus distribution is not yet supported. Follow this
+[issue](https://gitlab.com/gitlab-org/gitaly/issues/2476) for updates.
-In this setup guide we will start by configuring Praefect, then its child
-Gitaly nodes, and lastly the GitLab server configuration.
+Prepare all your new nodes by [installing
+GitLab](https://about.gitlab.com/install/).
+
+- 1 Praefect node (minimal storage required)
+- 3 Gitaly nodes (high CPU, high memory, fast storage)
+
+You will need the IP/host address for each node.
+
+1. `POSTGRESQL_SERVER_ADDRESS`: the IP/host address of the PostgreSQL server
+1. `PRAEFECT_SERVER_ADDRESS`: the IP/host address of the Praefect server
+1. `GITALY_SERVER_ADDRESS`: the IP/host address of each Gitaly node
#### Secrets
-We need to manage the following secrets and make them match across hosts:
-
-1. `GITLAB_SHELL_SECRET_TOKEN`: this is used by Git hooks to make
- callback HTTP API requests to GitLab when accepting a Git push. This
- secret is shared with GitLab Shell for legacy reasons.
-1. `PRAEFECT_EXTERNAL_TOKEN`: repositories hosted on your Praefect
- cluster can only be accessed by Gitaly clients that carry this
- token.
-1. `PRAEFECT_INTERNAL_TOKEN`: this token is used for replication
- traffic inside your Praefect cluster. This is distinct from
- `PRAEFECT_EXTERNAL_TOKEN` because Gitaly clients must not be able to
- access internal nodes of the Praefect cluster directly; that could
- lead to data loss.
+The communication between components is secured with different secrets, which
+are described below. Before you begin, generate a unique secret for each, and
+make note of it. This will make it easy to replace these placeholder tokens
+with secure tokens as you complete the setup process.
+
+1. `GITLAB_SHELL_SECRET_TOKEN`: this is used by Git hooks to make callback HTTP
+ API requests to GitLab when accepting a Git push. This secret is shared with
+ GitLab Shell for legacy reasons.
+1. `PRAEFECT_EXTERNAL_TOKEN`: repositories hosted on your Praefect cluster can
+ only be accessed by Gitaly clients that carry this token.
+1. `PRAEFECT_INTERNAL_TOKEN`: this token is used for replication traffic inside
+ your Praefect cluster. This is distinct from `PRAEFECT_EXTERNAL_TOKEN`
+ because Gitaly clients must not be able to access internal nodes of the
+ Praefect cluster directly; that could lead to data loss.
1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to
- PostgreSQL.
+ PostgreSQL.
We will note in the instructions below where these secrets are required.
-#### Network addresses
+### PostgreSQL
-1. `POSTGRESQL_SERVER_ADDRESS`: the host name or IP address of your PostgreSQL server
+NOTE: **Note:** don't reuse the GitLab application database for the Praefect
+database.
-#### PostgreSQL
+To complete this section you will need:
-To set up a Praefect cluster you need a highly available PostgreSQL
-server. You need PostgreSQL 9.6 or newer. Praefect needs to have a SQL
-user with the right to create databases.
+- 1 Praefect node
+- 1 PostgreSQL server (PostgreSQL 9.6 or newer)
+ - An SQL user with permissions to create databases
-In the instructions below we assume you have administrative access to
-your PostgreSQL server via `psql`. Depending on your environment, you
-may also be able to do this via the web interface of your cloud
-platform, or via your configuration management system, etc.
+During this section, we will configure the PostgreSQL server, from the Praefect
+node, using `psql` which is installed by GitLab Omnibus.
-Below we assume that you have administrative access as the `postgres`
-user. First open a `psql` session as the `postgres` user:
+1. SSH into the **Praefect** node and login as root:
-```shell
-/opt/gitlab/embedded/bin/psql -h POSTGRESQL_SERVER_ADDRESS -U postgres -d template1
-```
+ ```shell
+ sudo -i
+ ```
-Once you are connected, run the following command. Replace
-`PRAEFECT_SQL_PASSWORD` with the actual (random) password you
-generated for the `praefect` SQL user:
+1. Connect to the PostgreSQL server with administrative access. This is likely
+ the `postgres` user. The database `template1` is used because it is created
+ by default on all PostgreSQL servers.
-```sql
-CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD 'PRAEFECT_SQL_PASSWORD';
-\q
-```
+ ```shell
+ /opt/gitlab/embedded/bin/psql -U postgres -d template1 -h POSTGRESQL_SERVER_ADDRESS
+ ```
-Now connect as the `praefect` user to create the database. This has
-the side effect of verifying that you have access:
+ Create a new user `praefect` which will be used by Praefect. Replace
+ `PRAEFECT_SQL_PASSWORD` with the strong password you generated in the
+ preparation step.
-```shell
-/opt/gitlab/embedded/bin/psql -h POSTGRESQL_SERVER_ADDRESS -U praefect -d template1
-```
+ ```sql
+ CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD 'PRAEFECT_SQL_PASSWORD';
+ ```
-Once you have connected as the `praefect` user, run:
+1. Reconnect to the PostgreSQL server, this time as the `praefect` user:
-```sql
-CREATE DATABASE praefect_production WITH ENCODING=UTF8;
-\q
-```
+ ```shell
+ /opt/gitlab/embedded/bin/psql -U praefect -d template1 -h POSTGRESQL_SERVER_ADDRESS
+ ```
-#### Praefect
-
-On the Praefect node we disable all other services, including Gitaly. We list each
-Gitaly node that will be connected to Praefect as members of the `praefect` hash in `praefect['virtual_storages']`.
-
-In the example below, the Gitaly nodes are named `gitaly-N`. Note that one
-node is designated as primary by setting the primary to `true`.
-
-If you are using an uncrypted connection to Postgres, set `praefect['database_sslmode']` to false.
-
-If you are using an encrypted connection with a client certificate,
-`praefect['database_sslcert']` and `praefect['database_sslkey']` will need to be set.
-If you are using a custom CA, also set `praefect['database_sslrootcert']`:
-
-```ruby
-# /etc/gitlab/gitlab.rb on praefect server
-
-# Avoid running unnecessary services on the Gitaly server
-postgresql['enable'] = false
-redis['enable'] = false
-nginx['enable'] = false
-prometheus['enable'] = false
-grafana['enable'] = false
-unicorn['enable'] = false
-sidekiq['enable'] = false
-gitlab_workhorse['enable'] = false
-gitaly['enable'] = false
-
-# Prevent database connections during 'gitlab-ctl reconfigure'
-gitlab_rails['rake_cache_clear'] = false
-gitlab_rails['auto_migrate'] = false
-
-praefect['enable'] = true
-
-# Make Praefect accept connections on all network interfaces. You must use
-# firewalls to restrict access to this address/port.
-praefect['listen_addr'] = '0.0.0.0:2305'
-
-# Replace PRAEFECT_EXTERNAL_TOKEN with a real secret
-praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN'
-
-# Replace each instance of PRAEFECT_INTERNAL_TOKEN below with a real
-# secret, distinct from PRAEFECT_EXTERNAL_TOKEN.
-# Name of storage hash must match storage name in git_data_dirs on GitLab server.
-praefect['virtual_storages'] = {
- 'praefect' => {
- 'gitaly-1' => {
- # Replace GITALY_URL_OR_IP below with the real address to connect to.
- 'address' => 'tcp://GITALY_URL_OR_IP:8075',
- 'token' => 'PRAEFECT_INTERNAL_TOKEN',
- 'primary' => true
- },
- 'gitaly-2' => {
- # Replace GITALY_URL_OR_IP below with the real address to connect to.
- 'address' => 'tcp://GITALY_URL_OR_IP:8075',
- 'token' => 'PRAEFECT_INTERNAL_TOKEN'
- },
- 'gitaly-3' => {
- # Replace GITALY_URL_OR_IP below with the real address to connect to.
- 'address' => 'tcp://GITALY_URL_OR_IP:8075',
- 'token' => 'PRAEFECT_INTERNAL_TOKEN'
- }
- }
-}
-
-# Replace POSTGRESQL_SERVER below with a real IP/host address of the database.
-praefect['database_host'] = 'POSTGRESQL_SERVER_ADDRESS'
-praefect['database_port'] = 5432
-praefect['database_user'] = 'praefect'
-# Replace PRAEFECT_SQL_PASSWORD below with a real password of the database.
-praefect['database_password'] = 'PRAEFECT_SQL_PASSWORD'
-praefect['database_dbname'] = 'praefect_production'
-
-# Uncomment the line below if you do not want to use an encrypted
-# connection to PostgreSQL
-# praefect['database_sslmode'] = 'disable'
-
-# Uncomment and modify these lines if you are using a TLS client
-# certificate to connect to PostgreSQL
-# praefect['database_sslcert'] = '/path/to/client-cert'
-# praefect['database_sslkey'] = '/path/to/client-key'
-
-# Uncomment and modify this line if your PostgreSQL server uses a custom
-# CA
-# praefect['database_sslrootcert'] = '/path/to/rootcert'
-```
+ Create a new database `praefect_production`. By creating the database while
+ connected as the `praefect` user, we are confident they have access.
-Replace `POSTGRESQL_SERVER_ADDRESS`, `PRAEFECT_EXTERNAL_TOKEN`, `PRAEFECT_INTERNAL_TOKEN`,
-and `PRAEFECT_SQL_PASSWORD` with their respective values.
+ ```sql
+ CREATE DATABASE praefect_production WITH ENCODING=UTF8;
+ ```
-Save the file and reconfigure Praefect:
+The database used by Praefect is now configured.
-```shell
-sudo gitlab-ctl reconfigure
-```
+### Praefect
-After you reconfigure, verify that Praefect can reach PostgreSQL:
+To complete this section you will need:
-```shell
-sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping
-```
+- [Configured PostgreSQL server](#postgresql), including:
+ - IP/host address (`POSTGRESQL_SERVER_ADDRESS`)
+ - password (`PRAEFECT_SQL_PASSWORD`)
-If the check fails, make sure you have followed the steps correctly. If you edit `/etc/gitlab/gitlab.rb`,
-remember to run `sudo gitlab-ctl reconfigure` again before trying the
-`sql-ping` command.
-
-#### Gitaly
-
-Next we will configure each Gitaly server assigned to Praefect. Configuration for these
-is the same as a normal standalone Gitaly server, except that we use storage names and
-auth tokens from Praefect instead of GitLab.
-
-Below is an example configuration for `gitaly-1`, the only difference for the
-other Gitaly nodes is the storage name under `git_data_dirs`.
-
-Note that `gitaly['auth_token']` matches the `token` value listed under `praefect['virtual_storages']`
-on the Praefect node.
-
-```ruby
-# /etc/gitlab/gitlab.rb on gitaly node inside praefect cluster
-
-# Avoid running unnecessary services on the Gitaly server
-postgresql['enable'] = false
-redis['enable'] = false
-nginx['enable'] = false
-prometheus['enable'] = false
-grafana['enable'] = false
-unicorn['enable'] = false
-sidekiq['enable'] = false
-gitlab_workhorse['enable'] = false
-prometheus_monitoring['enable'] = false
-
-# Prevent database connections during 'gitlab-ctl reconfigure'
-gitlab_rails['rake_cache_clear'] = false
-gitlab_rails['auto_migrate'] = false
-
-# Replace GITLAB_SHELL_SECRET_TOKEN below with real secret
-gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
-
-# Configure the gitlab-shell API callback URL. Without this, `git push` will
-# fail. This can be your 'front door' GitLab URL or an internal load
-# balancer.
-# Possible values could be: 'http://10.23.101.53', 'https://gitlab.example.com',
-# etc. Please replace GITLAB_SERVER_ADDRESS with proper value and change schema
-# to 'https' in case you use encrypted connection.
-gitlab_rails['internal_api_url'] = 'http://GITLAB_SERVER_ADDRESS'
-
-# Replace PRAEFECT_INTERNAL_TOKEN below with a real secret.
-gitaly['auth_token'] = 'PRAEFECT_INTERNAL_TOKEN'
-
-# Make Gitaly accept connections on all network interfaces. You must use
-# firewalls to restrict access to this address/port.
-# Comment out following line if you only want to support TLS connections
-gitaly['listen_addr'] = "0.0.0.0:8075"
-
-git_data_dirs({
- # Update this to the name of this Gitaly server which will be later
- # exposed in the UI under "Admin area > Gitaly"
- "gitaly-1" => {
- "path" => "/var/opt/gitlab/git-data"
- }
-})
-```
+Praefect should be run on a dedicated node. Do not run Praefect on the
+application server, or a Gitaly node.
-Replace `GITLAB_SHELL_SECRET_TOKEN` and `PRAEFECT_INTERNAL_TOKEN`
-with their respective values.
+1. SSH into the **Praefect** node and login as root:
-For more information on Gitaly server configuration, see our [Gitaly documentation](index.md#3-gitaly-server-configuration).
+ ```shell
+ sudo -i
+ ```
-When finished editing the configuration file for each Gitaly server, run the
-reconfigure command to put changes into effect:
+1. Disable all other services by editing `/etc/gitlab/gitlab.rb`:
-```shell
-sudo gitlab-ctl reconfigure
-```
+ ```ruby
+ # Disable all other services on the Praefect node
+ postgresql['enable'] = false
+ redis['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ grafana['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ gitaly['enable'] = false
+
+ # Enable only the Praefect service
+ praefect['enable'] = true
+
+ # Prevent database connections during 'gitlab-ctl reconfigure'
+ gitlab_rails['rake_cache_clear'] = false
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. Configure **Praefect** to listen on network interfaces by editing
+ `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Make Praefect accept connections on all network interfaces.
+ # Use firewalls to restrict access to this address/port.
+ praefect['listen_addr'] = '0.0.0.0:2305'
+ ```
+
+1. Configure a strong `auth_token` for **Praefect** by editing
+ `/etc/gitlab/gitlab.rb`. This will be needed by clients outside the cluster
+ (like GitLab Shell) to communicate with the Praefect cluster :
+
+ ```ruby
+ praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN'
+ ```
+
+1. Configure **Praefect** to connect to the PostgreSQL database by editing
+ `/etc/gitlab/gitlab.rb`.
+
+ You will need to replace `POSTGRESQL_SERVER_ADDRESS` with the IP/host address
+ of the database, and `PRAEFECT_SQL_PASSWORD` with the strong password set
+ above.
+
+ ```ruby
+ praefect['database_host'] = 'POSTGRESQL_SERVER_ADDRESS'
+ praefect['database_port'] = 5432
+ praefect['database_user'] = 'praefect'
+ praefect['database_password'] = 'PRAEFECT_SQL_PASSWORD'
+ praefect['database_dbname'] = 'praefect_production'
+ ```
+
+ If you want to use a TLS client certificate, the options below can be used:
+
+ ```ruby
+ # Connect to PostreSQL using a TLS client certificate
+ # praefect['database_sslcert'] = '/path/to/client-cert'
+ # praefect['database_sslkey'] = '/path/to/client-key'
-When all Gitaly servers are configured, you can run the Praefect connection
+ # Trust a custom certificate authority
+ # praefect['database_sslrootcert'] = '/path/to/rootcert'
+ ```
+
+ By default Praefect will refuse to make an unencrypted connection to
+ PostgreSQL. You can override this by uncommenting the following line:
+
+ ```ruby
+ # praefect['database_sslmode'] = 'disable'
+ ```
+
+1. Configure the **Praefect** cluster to connect to each Gitaly node in the
+ cluster by editing `/etc/gitlab/gitlab.rb`.
+
+ In the example below we have configured one cluster named `praefect`. This
+ cluster has three Gitaly nodes `gitaly-1`, `gitaly-2`, and `gitaly-3`, which
+ will be replicas of each other.
+
+ Replace `PRAEFECT_INTERNAL_TOKEN` with a strong secret, which will be used by
+ Praefect when communicating with Gitaly nodes in the cluster. This token is
+ distinct from the `PRAEFECT_EXTERNAL_TOKEN`.
+
+ Replace `GITALY_HOST` with the IP/host address of the each Gitaly node.
+
+ More Gitaly nodes can be added to the cluster to increase the number of
+ replicas. More clusters can also be added for very large GitLab instances.
+
+ NOTE: **Note:** The `gitaly-1` node is currently denoted the primary. This
+ can be used to manually fail from one node to another. This will be removed
+ in the future to allow for automatic failover.
+
+ ```ruby
+ # Name of storage hash must match storage name in git_data_dirs on GitLab
+ # server ('praefect') and in git_data_dirs on Gitaly nodes ('gitaly-1')
+ praefect['virtual_storages'] = {
+ 'praefect' => {
+ 'gitaly-1' => {
+ 'address' => 'tcp://GITALY_HOST:8075',
+ 'token' => 'PRAEFECT_INTERNAL_TOKEN',
+ 'primary' => true
+ },
+ 'gitaly-2' => {
+ 'address' => 'tcp://GITALY_HOST:8075',
+ 'token' => 'PRAEFECT_INTERNAL_TOKEN'
+ },
+ 'gitaly-3' => {
+ 'address' => 'tcp://GITALY_HOST:8075',
+ 'token' => 'PRAEFECT_INTERNAL_TOKEN'
+ }
+ }
+ }
+ ```
+
+1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure):
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+1. Verify that Praefect can reach PostgreSQL:
+
+ ```shell
+ sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping
+ ```
+
+ If the check fails, make sure you have followed the steps correctly. If you
+ edit `/etc/gitlab/gitlab.rb`, remember to run `sudo gitlab-ctl reconfigure`
+ again before trying the `sql-ping` command.
+
+### Gitaly
+
+NOTE: **Note:** Complete these steps for **each** Gitaly node.
+
+To complete this section you will need:
+
+- [Configured Praefect node](#praefect)
+- 3 (or more) servers, with GitLab installed, to be configured as Gitaly nodes.
+ These should be dedicated nodes, do not run other services on these nodes.
+
+Every Gitaly server assigned to the Praefect cluster needs to be configured. The
+configuration is the same as a normal [standalone Gitaly server](../index.md),
+except:
+
+- the storage names are exposed to Praefect, not GitLab
+- the secret token is shared with Praefect, not GitLab
+
+The configuration of all Gitaly nodes in the Praefect cluster can be identical,
+because we rely on Praefect to route operations correctly.
+
+Particular attention should be shown to:
+
+- the `gitaly['auth_token']` configured in this section must match the `token`
+ value under `praefect['virtual_storages']` on the Praefect node. This was set
+ in the [previous section](#praefect). This document uses the placeholder
+ `PRAEFECT_INTERNAL_TOKEN` throughout.
+- the storage names in `git_data_dirs` configured in this section must match the
+ storage names under `praefect['virtual_storages']` on the Praefect node. This
+ was set in the [previous section](#praefect). This document uses `gitaly-1`,
+ `gitaly-2`, and `gitaly-3` as Gitaly storage names.
+
+For more information on Gitaly server configuration, see our [Gitaly
+documentation](index.md#3-gitaly-server-configuration).
+
+1. SSH into the **Gitaly** node and login as root:
+
+ ```shell
+ sudo -i
+ ```
+
+1. Disable all other services by editing `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Disable all other services on the Praefect node
+ postgresql['enable'] = false
+ redis['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ grafana['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ prometheus_monitoring['enable'] = false
+
+ # Enable only the Praefect service
+ gitaly['enable'] = true
+
+ # Prevent database connections during 'gitlab-ctl reconfigure'
+ gitlab_rails['rake_cache_clear'] = false
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. Configure **Gitaly** to listen on network interfaces by editing
+ `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Make Gitaly accept connections on all network interfaces.
+ # Use firewalls to restrict access to this address/port.
+ gitaly['listen_addr'] = '0.0.0.0:8075'
+ ```
+
+1. Configure a strong `auth_token` for **Gitaly** by editing
+ `/etc/gitlab/gitlab.rb`. This will be needed by clients to communicate with
+ this Gitaly nodes. Typically, this token will be the same for all Gitaly
+ nodes.
+
+ ```ruby
+ gitaly['auth_token'] = 'PRAEFECT_INTERNAL_TOKEN'
+ ```
+
+1. Configure the GitLab Shell `secret_token`, and `internal_api_url` which are
+ needed for `git push` operations.
+
+ If you have already configured [Gitaly on its own server](../index.md)
+
+ ```ruby
+ gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
+
+ # Configure the gitlab-shell API callback URL. Without this, `git push` will
+ # fail. This can be your front door GitLab URL or an internal load balancer.
+ # Examples: 'https://example.gitlab.com', 'http://1.2.3.4'
+ gitlab_rails['internal_api_url'] = 'GITLAB_SERVER_URL'
+ ```
+
+1. Configure the storage location for Git data by setting `git_data_dirs` in
+ `/etc/gitlab/gitlab.rb`. Each Gitaly node should have a unique storage name
+ (eg `gitaly-1`).
+
+ Instead of configuring `git_data_dirs` uniquely for each Gitaly node, it is
+ often easier to have include the configuration for all Gitaly nodes on every
+ Gitaly node. This is supported because the Praefect `virtual_storages`
+ configuration maps each storage name (eg `gitaly-1`) to a specific node, and
+ requests are routed accordingly. This means every Gitaly node in your fleet
+ can share the same configuration.
+
+ ```ruby
+ # You can include the data dirs for all nodes in the same config, because
+ # Praefect will only route requests according to the addresses provided in the
+ # prior step.
+ git_data_dirs({
+ "gitaly-1" => {
+ "path" => "/var/opt/gitlab/git-data"
+ },
+ "gitaly-2" => {
+ "path" => "/var/opt/gitlab/git-data"
+ },
+ "gitaly-3" => {
+ "path" => "/var/opt/gitlab/git-data"
+ }
+ })
+ ```
+
+1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure Gitaly](../restart_gitlab.md#omnibus-gitlab-reconfigure):
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+**Complete these steps for each Gitaly node!**
+
+After all Gitaly nodes are configured, you can run the Praefect connection
checker to verify Praefect can connect to all Gitaly servers in the Praefect
-config. This can be done by running the following command on the Praefect
-server:
+config.
-```shell
-sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes
-```
+1. SSH into the **Praefect** node and run the Praefect connection checker:
-#### GitLab
-
-When Praefect is running, it should be exposed as a storage to GitLab. This
-is done through setting the `git_data_dirs`. Assuming the default storage
-is present, there should be two storages available to GitLab:
-
-```ruby
-# /etc/gitlab/gitlab.rb on gitlab server
-
-# Replace PRAEFECT_URL_OR_IP below with real address Praefect can be accessed at.
-# Replace PRAEFECT_EXTERNAL_TOKEN below with real secret.
-git_data_dirs({
- "default" => {
- "path" => "/var/opt/gitlab/git-data"
- },
- "praefect" => {
- "gitaly_address" => "tcp://PRAEFECT_URL_OR_IP:2305",
- "gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
- }
-})
-
-# Replace GITLAB_SHELL_SECRET_TOKEN below with real secret
-gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
-
-# Possible values could be: 'http://10.23.101.53', 'https://gitlab.example.com',
-# etc. Please replace GITLAB_SERVER_ADDRESS with proper value and change schema
-# to 'https' in case you use encrypted connection. For more info please refer
-# to https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-the-external-url-for-gitlab
-external_url "http://<GITLAB_SERVER_ADDRESS>"
-```
+ ```shell
+ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes
+ ```
-Replace `GITLAB_SHELL_SECRET_TOKEN` and `PRAEFECT_EXTERNAL_TOKEN`
-with their respective values.
+### GitLab
-Note that the storage name used is the same as the `praefect['virtual_storage_name']` set
-on the Praefect node.
+To complete this section you will need:
-Save your changes and reconfigure GitLab:
+- [Configured Praefect node](#praefect)
+- [Configured Gitaly nodes](#gitaly)
-```shell
-sudo gitlab-ctl reconfigure
-```
+The Praefect cluster needs to be exposed as a storage location to the GitLab
+application. This is done by updating the `git_data_dirs`.
+
+Particular attention should be shown to:
+
+- the storage name added to `git_data_dirs` in this section must match the
+ storage name under `praefect['virtual_storages']` on the Praefect node. This
+ was set in the [Praefect](#praefect) section of this guide. This document uses
+ `praefect` as the Praefect storage name.
+
+1. SSH into the **GitLab** node and login as root:
+
+ ```shell
+ sudo -i
+ ```
+
+1. Add the Praefect cluster as a storage location by editing
+ `/etc/gitlab/gitlab.rb`.
+
+ You will need to replace:
+
+ - `PRAEFECT_URL_OR_IP` with the IP/host address of the Praefect node
+ - `PRAEFECT_EXTERNAL_TOKEN` with the real secret
-Run `sudo gitlab-rake gitlab:gitaly:check` to confirm that GitLab can reach Praefect.
+ ```ruby
+ git_data_dirs({
+ "default" => {
+ "path" => "/var/opt/gitlab/git-data"
+ },
+ "praefect" => {
+ "gitaly_address" => "tcp://PRAEFECT_URL_OR_IP:2305",
+ "gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
+ }
+ })
+ ```
-### Testing Praefect
+1. Configure the `gitlab_shell['secret_token']` so that callbacks from Gitaly
+ nodes during a `git push` are properly authenticated by editing
+ `/etc/gitlab/gitlab.rb`:
+
+ You will need to replace `GITLAB_SHELL_SECRET_TOKEN` with the real secret.
+
+ ```ruby
+ gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
+ ```
+
+1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure):
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+1. Verify that GitLab can reach Praefect:
+
+ ```shell
+ sudo gitlab-rake gitlab:gitaly:check
+ ```
+
+1. Update the **Repository storage** settings from **Admin Area > Settings >
+ Repository > Repository storage** to make the newly configured Praefect
+ cluster the storage location for new Git repositories.
+
+ - Deselect the **default** storage location
+ - Select the **praefect** storage location
+
+1. Verify everything is still working by creating a new project. Check the
+ "Initialize repository with a README" box so that there is content in the
+ repository that viewed. If the project is created, and you can see the
+ README file, it works!
+
+Congratulations! You have configured a highly available Praefect cluster, and
+
+## Migrating existing repositories to Praefect
+
+If your GitLab instance already has repositories, these won't be migrated
+automatically.
+
+Repositories may be moved from one storage location using the [Repository
+API](../../api/projects.html#edit-project):
+
+```shell
+curl --request PUT \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ --data "repository_storage=praefect" \
+ https://example.gitlab.com/api/v4/projects/123
+```
-To test Praefect, first set it as the default storage node for new projects
-using **Admin Area > Settings > Repository > Repository storage**. Next,
-create a new project and check the "Initialize repository with a README" box.
+## Debugging Praefect
If you receive an error, check `/var/log/gitlab/gitlab-rails/production.log`.
diff --git a/doc/api/commits.md b/doc/api/commits.md
index ca82eb4c442..90632a2faa0 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -42,7 +42,8 @@ Example response:
"message": "Replace sanitize with escape once",
"parent_ids": [
"6104942438c14ec7bd21c6cd5bd995272b3faff6"
- ]
+ ],
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
},
{
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
@@ -56,7 +57,8 @@ Example response:
"message": "Sanitize for network graph",
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
- ]
+ ],
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
}
]
```
@@ -156,7 +158,8 @@ Example response:
"deletions": 2,
"total": 4
},
- "status": null
+ "status": null,
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
}
```
@@ -235,7 +238,8 @@ Example response:
"deletions": 10,
"total": 25
},
- "status": "running"
+ "status": "running",
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/6104942438c14ec7bd21c6cd5bd995272b3faff6"
}
```
@@ -314,7 +318,8 @@ Example response:
"message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"parent_ids": [
"a738f717824ff53aebad8b090c1b79a14f2bd9e8"
- ]
+ ],
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/8b090c1b79a14f2bd9e8a738f717824ff53aebad"
}
```
@@ -370,7 +375,8 @@ Example response:
"authored_date":"2018-11-08T15:55:26.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
- "committed_date":"2018-11-08T15:55:26.000Z"
+ "committed_date":"2018-11-08T15:55:26.000Z",
+ "web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/8b090c1b79a14f2bd9e8a738f717824ff53aebad"
}
```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index e48cb78b2cc..a4fc3f95c5c 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -492,6 +492,7 @@ Parameters:
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
| `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). |
| `emails_disabled` | boolean | no | Disable email notifications |
+| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/36681) |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
@@ -553,6 +554,7 @@ PUT /groups/:id
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
| `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). |
| `emails_disabled` | boolean | no | Disable email notifications |
+| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/36681) |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
diff --git a/lib/api/entities/commit.rb b/lib/api/entities/commit.rb
index 7ce97c2c3d8..3eaf896f1ac 100644
--- a/lib/api/entities/commit.rb
+++ b/lib/api/entities/commit.rb
@@ -9,6 +9,10 @@ module API
expose :safe_message, as: :message
expose :author_name, :author_email, :authored_date
expose :committer_name, :committer_email, :committed_date
+
+ expose :web_url do |commit, _options|
+ Gitlab::UrlBuilder.build(commit)
+ end
end
end
end
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index 25701ff683d..f3dfc093926 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -11,6 +11,8 @@ module API
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the group'
+ # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
+ optional :avatar, type: File, desc: 'Avatar image for the group' # rubocop:disable Scalability/FileUploads
optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication'
optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced'
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 3040c3c27c6..e8234a9285c 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -5,12 +5,17 @@ module API
include PaginationParams
before { authenticate! }
+ before { check_snippets_enabled }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
+ def check_snippets_enabled
+ forbidden! unless user_project.feature_available?(:snippets, current_user)
+ end
+
def handle_project_member_errors(errors)
if errors[:project_access].any?
error!(errors[:project_access], 422)
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index 95313966133..bdaec67108d 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -26,6 +26,26 @@ module API
with: Entities::RemoteMirror
end
+ desc 'Create remote mirror for a project' do
+ success Entities::RemoteMirror
+ end
+ params do
+ requires :url, type: String, desc: 'The URL for a remote mirror'
+ optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled'
+ optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored'
+ end
+ post ':id/remote_mirrors' do
+ create_params = declared_params(include_missing: false)
+
+ new_mirror = user_project.remote_mirrors.create(create_params)
+
+ if new_mirror.persisted?
+ present new_mirror, with: Entities::RemoteMirror
+ else
+ render_validation_error!(new_mirror)
+ end
+ end
+
desc 'Update the attributes of a single remote mirror' do
success Entities::RemoteMirror
end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 4bedf7a301e..cc53e3b7577 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -13,7 +13,8 @@ module Gitlab
end
def url
- case object
+ # Objects are sometimes wrapped in a BatchLoader instance
+ case object.itself
when Commit
commit_url
when Issue
@@ -33,7 +34,7 @@ module Gitlab
when User
user_url(object)
else
- raise NotImplementedError.new("No URL builder defined for #{object.class}")
+ raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end
end
diff --git a/package.json b/package.json
index 2d1905ab120..67cd46cbbac 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@gitlab/at.js": "^1.5.5",
- "@gitlab/svgs": "^1.104.0",
+ "@gitlab/svgs": "^1.105.0",
"@gitlab/ui": "^9.20.0",
"@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2",
diff --git a/spec/finders/fork_targets_finder_spec.rb b/spec/finders/fork_targets_finder_spec.rb
index e7110c33071..f8c03cdf9b3 100644
--- a/spec/finders/fork_targets_finder_spec.rb
+++ b/spec/finders/fork_targets_finder_spec.rb
@@ -28,8 +28,8 @@ describe ForkTargetsFinder do
end
describe '#execute' do
- it 'returns all user manageable namespaces except project namespace' do
- expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group])
+ it 'returns all user manageable namespaces' do
+ expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group, project.namespace])
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/basic.json b/spec/fixtures/api/schemas/public_api/v4/commit/basic.json
index 9d99628a286..da99e99c692 100644
--- a/spec/fixtures/api/schemas/public_api/v4/commit/basic.json
+++ b/spec/fixtures/api/schemas/public_api/v4/commit/basic.json
@@ -12,7 +12,8 @@
"authored_date",
"committer_name",
"committer_email",
- "committed_date"
+ "committed_date",
+ "web_url"
],
"properties" : {
"id": { "type": ["string", "null"] },
@@ -32,6 +33,7 @@
"authored_date": { "type": "date" },
"committer_name": { "type": "string" },
"committer_email": { "type": "string" },
- "committed_date": { "type": "date" }
+ "committed_date": { "type": "date" },
+ "web_url": { "type": "string" }
}
}
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 11bbf444b1d..c2eb1b4c25d 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -14,6 +14,18 @@ describe Gitlab::UrlBuilder do
end
end
+ context 'when passing a batch loaded Commit' do
+ it 'returns a proper URL' do
+ commit = BatchLoader.for(:commit).batch do |batch, loader|
+ batch.each { |commit| loader.call(:commit, build_stubbed(:commit)) }
+ end
+
+ url = described_class.build(commit)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.full_path}/-/commit/#{commit.id}"
+ end
+ end
+
context 'when passing an Issue' do
it 'returns a proper URL' do
issue = build_stubbed(:issue, iid: 42)
@@ -160,7 +172,7 @@ describe Gitlab::UrlBuilder do
project = build_stubbed(:project)
expect { described_class.build(project) }
- .to raise_error(NotImplementedError, 'No URL builder defined for Project')
+ .to raise_error(NotImplementedError, "No URL builder defined for #{project.inspect}")
end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index cf1690df9ba..76051ecb177 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2813,6 +2813,30 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#created_successfully?' do
+ subject { pipeline.created_successfully? }
+
+ context 'when pipeline is not persisted' do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when pipeline is persisted' do
+ context 'when pipeline has failure reasons' do
+ let(:pipeline) { create(:ci_pipeline, failure_reason: :config_error) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when pipeline has no failure reasons' do
+ let(:pipeline) { create(:ci_pipeline, failure_reason: nil) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
describe '#parent_pipeline' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -2960,8 +2984,7 @@ describe Ci::Pipeline, :mailer do
it 'can not update bridge status if is not active' do
bridge.success!
- expect { pipeline.update_bridge_status! }
- .to raise_error Ci::Pipeline::BridgeStatusError
+ expect { pipeline.update_bridge_status! }.not_to change { bridge.status }
end
end
end
@@ -2992,9 +3015,12 @@ describe Ci::Pipeline, :mailer do
end
describe '#update_bridge_status!' do
- it 'can not update upstream job status' do
- expect { pipeline.update_bridge_status! }
- .to raise_error ArgumentError
+ it 'tracks an ArgumentError and does not update upstream job status' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(instance_of(ArgumentError), pipeline_id: pipeline.id)
+
+ pipeline.update_bridge_status!
end
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index fb564bb398b..7dfa239cd1e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -21,6 +21,47 @@ describe API::Groups do
group2.add_owner(user2)
end
+ shared_examples 'group avatar upload' do
+ context 'when valid' do
+ let(:file_path) { 'spec/fixtures/banana_sample.gif' }
+
+ it 'returns avatar url in response' do
+ make_upload_request
+
+ group_id = json_response['id']
+ expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
+ '-/system/group/avatar/'\
+ "#{group_id}/banana_sample.gif")
+ end
+ end
+
+ context 'when invalid' do
+ shared_examples 'invalid file upload request' do
+ it 'returns 400' do
+ make_upload_request
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.message).to eq('Bad Request')
+ expect(json_response['message'].to_s).to match(/#{message}/)
+ end
+ end
+
+ context 'when file format is not supported' do
+ let(:file_path) { 'spec/fixtures/doc_sample.txt' }
+ let(:message) { 'file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico' }
+
+ it_behaves_like 'invalid file upload request'
+ end
+
+ context 'when file format is not supported' do
+ let(:file_path) { 'spec/fixtures/big-image.png' }
+ let(:message) { 'is too big' }
+
+ it_behaves_like 'invalid file upload request'
+ end
+ end
+ end
+
describe "GET /groups" do
context "when unauthenticated" do
it "returns public groups" do
@@ -539,6 +580,15 @@ describe API::Groups do
describe 'PUT /groups/:id' do
let(:new_group_name) { 'New Group'}
+ it_behaves_like 'group avatar upload' do
+ def make_upload_request
+ group_param = {
+ avatar: fixture_file_upload(file_path)
+ }
+ put api("/groups/#{group1.id}", user1), params: group_param
+ end
+ end
+
context 'when authenticated as the group owner' do
it 'updates the group' do
put api("/groups/#{group1.id}", user1), params: {
@@ -940,6 +990,16 @@ describe API::Groups do
end
describe "POST /groups" do
+ it_behaves_like 'group avatar upload' do
+ def make_upload_request
+ params = attributes_for_group_api(request_access_enabled: false).tap do |attrs|
+ attrs[:avatar] = fixture_file_upload(file_path)
+ end
+
+ post api("/groups", user3), params: params
+ end
+ end
+
context "when authenticated as user without group permissions" do
it "does not create group" do
group = attributes_for_group_api
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index f0ab2f26900..d8fac47d6f6 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1150,12 +1150,16 @@ describe API::MergeRequests do
describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do
before do
- stub_ci_pipeline_yaml_file(YAML.dump({
+ stub_ci_pipeline_yaml_file(ci_yaml)
+ end
+
+ let(:ci_yaml) do
+ YAML.dump({
rspec: {
script: 'ls',
only: ['merge_requests']
}
- }))
+ })
end
let(:project) do
@@ -1208,6 +1212,18 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ context 'when the .gitlab-ci.yml file is invalid' do
+ let(:ci_yaml) { 'invalid yaml file' }
+
+ it 'creates a failed pipeline' do
+ expect { request }.to change(Ci::Pipeline, :count).by(1)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_a Hash
+ expect(merge_request.pipelines_for_merge_request.last).to be_failed
+ expect(merge_request.pipelines_for_merge_request.last).to be_config_error
+ end
+ end
end
describe 'POST /projects/:id/merge_requests' do
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 16903d9d6d0..ba5de430f7d 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -6,6 +6,12 @@ describe API::ProjectSnippets do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
+ let_it_be(:project_no_snippets) { create(:project, :snippets_disabled) }
+
+ before do
+ project_no_snippets.add_developer(admin)
+ project_no_snippets.add_developer(user)
+ end
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
let(:snippet) { create(:project_snippet, :public, project: project) }
@@ -32,6 +38,12 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:forbidden)
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123/user_agent_detail", admin) }
+ end
+ end
end
describe 'GET /projects/:project_id/snippets/' do
@@ -63,6 +75,12 @@ describe API::ProjectSnippets do
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project_no_snippets.id}/snippets", user) }
+ end
+ end
end
describe 'GET /projects/:project_id/snippets/:id' do
@@ -85,6 +103,12 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Not found')
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
+ end
+ end
end
describe 'POST /projects/:project_id/snippets/' do
@@ -244,11 +268,17 @@ describe API::ProjectSnippets do
end
end
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { post api("/projects/#{project_no_snippets.id}/snippets", user), params: params }
+ end
+ end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
let(:visibility_level) { Snippet::PUBLIC }
- let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) }
+ let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level, project: project) }
it 'updates snippet' do
new_content = 'New content'
@@ -354,10 +384,16 @@ describe API::ProjectSnippets do
end
end
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { put api("/projects/#{project_no_snippets.id}/snippets/123", admin), params: { description: 'foo' } }
+ end
+ end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
- let(:snippet) { create(:project_snippet, author: admin) }
+ let(:snippet) { create(:project_snippet, author: admin, project: project) }
it 'deletes snippet' do
delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
@@ -375,10 +411,16 @@ describe API::ProjectSnippets do
it_behaves_like '412 response' do
let(:request) { api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) }
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { delete api("/projects/#{project_no_snippets.id}/snippets/123", admin) }
+ end
+ end
end
describe 'GET /projects/:project_id/snippets/:id/raw' do
- let(:snippet) { create(:project_snippet, author: admin) }
+ let(:snippet) { create(:project_snippet, author: admin, project: project) }
it 'returns raw text' do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
@@ -394,5 +436,11 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Snippet Not Found')
end
+
+ context 'with snippets disabled' do
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123/raw", admin) }
+ end
+ end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 59c394d8d8d..858fdc783ee 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2935,6 +2935,26 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['name']).to eq(['has already been taken'])
end
+
+ it 'forks to the same namespace with alternative path and name' do
+ post api("/projects/#{project.id}/fork", user), params: { path: 'path_2', name: 'name_2' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['name']).to eq('name_2')
+ expect(json_response['path']).to eq('path_2')
+ expect(json_response['owner']['id']).to eq(user.id)
+ expect(json_response['namespace']['id']).to eq(user.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ expect(json_response['import_status']).to eq('scheduled')
+ end
+
+ it 'fails to fork to the same namespace without alternative path and name' do
+ post api("/projects/#{project.id}/fork", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(json_response['message']['path']).to eq(['has already been taken'])
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ end
end
context 'when unauthenticated' do
diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb
index 065d9c7ca5b..2186fe375ac 100644
--- a/spec/requests/api/remote_mirrors_spec.rb
+++ b/spec/requests/api/remote_mirrors_spec.rb
@@ -39,6 +39,54 @@ describe API::RemoteMirrors do
end
end
+ describe 'POST /projects/:id/remote_mirrors' do
+ let(:route) { "/projects/#{project.id}/remote_mirrors" }
+
+ shared_examples 'creates a remote mirror' do
+ it 'creates a remote mirror and returns reponse' do
+ project.add_maintainer(user)
+
+ post api(route, user), params: params
+
+ enabled = params.fetch(:enabled, false)
+ expect(response).to have_gitlab_http_status(:success)
+ expect(response).to match_response_schema('remote_mirror')
+ expect(json_response['enabled']).to eq(enabled)
+ end
+ end
+
+ it 'requires `admin_remote_mirror` permission' do
+ post api(route, developer)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'creates a remote mirror' do
+ context 'disabled by default' do
+ let(:params) { { url: 'https://foo:bar@test.com' } }
+
+ it_behaves_like 'creates a remote mirror'
+ end
+
+ context 'enabled' do
+ let(:params) { { url: 'https://foo:bar@test.com', enabled: true } }
+
+ it_behaves_like 'creates a remote mirror'
+ end
+ end
+
+ it 'returns error if url is invalid' do
+ project.add_maintainer(user)
+
+ post api(route, user), params: {
+ url: 'ftp://foo:bar@test.com'
+ }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['url']).to eq(["is blocked: Only allowed schemes are ssh, git, http, https"])
+ end
+ end
+
describe 'PUT /projects/:id/remote_mirrors/:mirror_id' do
let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } }
let(:mirror) { project.remote_mirrors.first }
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
index 09d44bcea0a..667ad532fb0 100644
--- a/spec/services/ci/create_cross_project_pipeline_service_spec.rb
+++ b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
@@ -116,6 +116,28 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
expect(bridge.reload).to be_success
end
+ context 'when bridge job has already any downstream pipelines' do
+ before do
+ bridge.sourced_pipelines.create!(
+ source_pipeline: bridge.pipeline,
+ source_project: bridge.project,
+ project: bridge.project,
+ pipeline: create(:ci_pipeline, project: bridge.project)
+ )
+ end
+
+ it 'logs an error and exits' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(
+ instance_of(Ci::CreateCrossProjectPipelineService::DuplicateDownstreamPipelineError),
+ bridge_id: bridge.id, project_id: bridge.project.id)
+ .and_call_original
+ expect(Ci::CreatePipelineService).not_to receive(:new)
+ expect(service.execute(bridge)).to be_nil
+ end
+ end
+
context 'when target ref is not specified' do
let(:trigger) do
{ trigger: { project: downstream_project.full_path } }
@@ -149,13 +171,11 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
expect(pipeline.source_bridge).to be_a ::Ci::Bridge
end
- it 'does not update bridge status when downstream pipeline gets processed' do
+ it 'updates the bridge status when downstream pipeline gets processed' do
pipeline = service.execute(bridge)
expect(pipeline.reload).to be_failed
- # TODO: This should change to failed once #198354 gets fixed.
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25706
- expect(bridge.reload).to be_pending
+ expect(bridge.reload).to be_failed
end
end
@@ -242,6 +262,22 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
it_behaves_like 'creates a child pipeline'
+ it 'updates the bridge job to success' do
+ expect { service.execute(bridge) }.to change { bridge.status }.to 'success'
+ end
+
+ context 'when bridge uses "depend" strategy' do
+ let(:trigger) do
+ {
+ trigger: { include: 'child-pipeline.yml', strategy: 'depend' }
+ }
+ end
+
+ it 'does not update the bridge job status' do
+ expect { service.execute(bridge) }.not_to change { bridge.status }
+ end
+ end
+
context 'when latest sha for the ref changed in the meantime' do
before do
upstream_project.repository.create_file(
@@ -298,6 +334,34 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end
end
+ context 'when downstream pipeline creation errors out' do
+ let(:stub_config) { false }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
+ end
+
+ it 'creates only one new pipeline' do
+ expect { service.execute(bridge) }
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+
+ it 'creates a new pipeline in the downstream project' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.user).to eq bridge.user
+ expect(pipeline.project).to eq downstream_project
+ end
+
+ it 'drops the bridge' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.reload).to be_failed
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
+ end
+ end
+
context 'when bridge job has YAML variables defined' do
before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }]
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index d7357cf4d0b..acd14005c69 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -129,6 +129,21 @@ describe Git::BranchPushService, services: true do
end
end
end
+
+ context 'when .gitlab-ci.yml file is invalid' do
+ before do
+ stub_ci_pipeline_yaml_file('invalid yaml file')
+ end
+
+ it 'persists an error pipeline' do
+ expect { subject }.to change { Ci::Pipeline.count }
+
+ pipeline = Ci::Pipeline.last
+ expect(pipeline).to be_push
+ expect(pipeline).to be_failed
+ expect(pipeline).to be_config_error
+ end
+ end
end
describe "Updates merge requests" do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index aebead481ce..c34f81901ef 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -177,18 +177,18 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
describe 'Pipelines for merge requests' do
before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
+ stub_ci_pipeline_yaml_file(config)
end
context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do
- {
+ YAML.dump({
test: {
stage: 'test',
script: 'echo',
only: ['merge_requests']
}
- }
+ })
end
it 'creates a detached merge request pipeline and sets it as a head pipeline' do
@@ -269,12 +269,12 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
context "when .gitlab-ci.yml does not have merge_requests keywords" do
let(:config) do
- {
+ YAML.dump({
test: {
stage: 'test',
script: 'echo'
}
- }
+ })
end
it 'does not create a detached merge request pipeline' do
@@ -284,6 +284,19 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
expect(merge_request.pipelines_for_merge_request.count).to eq(0)
end
end
+
+ context 'when .gitlab-ci.yml is invalid' do
+ let(:config) { 'invalid yaml file' }
+
+ it 'persists a pipeline with config error' do
+ expect(merge_request).to be_persisted
+
+ merge_request.reload
+ expect(merge_request.pipelines_for_merge_request.count).to eq(1)
+ expect(merge_request.pipelines_for_merge_request.last).to be_failed
+ expect(merge_request.pipelines_for_merge_request.last).to be_config_error
+ end
+ end
end
it 'increments the usage data counter of create event' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index b67779a912d..4f052fa3edb 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -148,7 +148,7 @@ describe MergeRequests::RefreshService do
describe 'Pipelines for merge requests' do
before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
+ stub_ci_pipeline_yaml_file(config)
end
subject { service.new(project, @user).execute(@oldrev, @newrev, ref) }
@@ -158,13 +158,13 @@ describe MergeRequests::RefreshService do
context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do
- {
+ YAML.dump({
test: {
stage: 'test',
script: 'echo',
only: ['merge_requests']
}
- }
+ })
end
it 'create detached merge request pipeline with commits' do
@@ -255,16 +255,28 @@ describe MergeRequests::RefreshService do
end.not_to change { @merge_request.pipelines_for_merge_request.count }
end
end
+
+ context 'when the pipeline should be skipped' do
+ it 'saves a skipped detached merge request pipeline' do
+ project.repository.create_file(@user, 'new-file.txt', 'A new file',
+ message: '[skip ci] This is a test',
+ branch_name: 'master')
+
+ expect { subject }
+ .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
+ expect(@merge_request.pipelines_for_merge_request.last).to be_skipped
+ end
+ end
end
context "when .gitlab-ci.yml does not have merge_requests keywords" do
let(:config) do
- {
+ YAML.dump({
test: {
stage: 'test',
script: 'echo'
}
- }
+ })
end
it 'does not create a detached merge request pipeline' do
@@ -272,6 +284,40 @@ describe MergeRequests::RefreshService do
.not_to change { @merge_request.pipelines_for_merge_request.count }
end
end
+
+ context 'when .gitlab-ci.yml is invalid' do
+ let(:config) { 'invalid yaml file' }
+
+ it 'persists a pipeline with config error' do
+ expect { subject }
+ .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
+ expect(@merge_request.pipelines_for_merge_request.last).to be_failed
+ expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
+ end
+ end
+
+ context 'when .gitlab-ci.yml file is valid but has a logical error' do
+ let(:config) do
+ YAML.dump({
+ build: {
+ script: 'echo "Valid yaml syntax, but..."',
+ only: ['master']
+ },
+ test: {
+ script: 'echo "... I depend on build, which does not run."',
+ only: ['merge_request'],
+ needs: ['build']
+ }
+ })
+ end
+
+ it 'persists a pipeline with config error' do
+ expect { subject }
+ .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
+ expect(@merge_request.pipelines_for_merge_request.last).to be_failed
+ expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
+ end
+ end
end
context 'push to origin repo source branch' do
diff --git a/yarn.lock b/yarn.lock
index b48f6ef39f1..af76917f88d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -796,10 +796,10 @@
dependencies:
vue-eslint-parser "^7.0.0"
-"@gitlab/svgs@^1.104.0":
- version "1.104.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.104.0.tgz#ebbf99788d74b7224f116f1c0040fa0c90034d99"
- integrity sha512-lWg/EzxFdbx4YIdDWB2p5ag6Cna78AYGET8nXQYXYwd21/U3wKXKL7vsGR4kOxe1goA9ZAYG9eY+MK7cf+X2cA==
+"@gitlab/svgs@^1.105.0":
+ version "1.105.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.105.0.tgz#9686f8696594a5f22de11af2b81fdcceb715f4f2"
+ integrity sha512-2wzZXe2b7DnGyL7FTbPq0dSpk+gjkq4SBTNtMrqdwX2qaM+XJB50XaMm17kdY5V1bBkMgbc7JJ2vgbLxhS/CkQ==
"@gitlab/ui@^9.20.0":
version "9.20.0"