diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 18:08:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 18:08:16 +0000 |
commit | e9c2bf267862e22c0770cc7b3a1ed97a8b87a7fd (patch) | |
tree | 7b778e44f210132af1233ceb8801b388ac3519f5 | |
parent | 946771d0b016ae92b15a60bc3290a33b94191ffe (diff) | |
download | gitlab-ce-e9c2bf267862e22c0770cc7b3a1ed97a8b87a7fd.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 Binary files differnew file mode 100644 index 00000000000..3937789094c --- /dev/null +++ b/doc/administration/gitaly/img/praefect_architecture_v12_9.png 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" |