Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									946771d0b0
								
							
						
					
					
						commit
						e9c2bf2678
					
				|  | @ -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({ | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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? | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Allow to fork to the same namespace and different path via API call | ||||
| merge_request: 26062 | ||||
| author: | ||||
| type: fixed | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Project Snippets API endpoints check feature status | ||||
| merge_request: 26064 | ||||
| author: | ||||
| type: performance | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Add support for configuring remote mirrors via API | ||||
| merge_request: 25825 | ||||
| author: Rajendra Kadam | ||||
| type: added | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Drop bridge if downstream pipeline has errors | ||||
| merge_request: 25706 | ||||
| author: | ||||
| type: fixed | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Add avatar upload support for create and update group APIs | ||||
| merge_request: 25751 | ||||
| author: Rajendra Kadam | ||||
| type: added | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Add web_url attribute to API response for Commits | ||||
| merge_request: 26173 | ||||
| author: | ||||
| type: added | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Remove unused Snippets#content_types method | ||||
| merge_request: 26306 | ||||
| author: | ||||
| type: other | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Send credentials with GraphQL fetch requests | ||||
| merge_request: 26386 | ||||
| author: | ||||
| type: fixed | ||||
|  | @ -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)** | ||||
| 
 | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 155 KiB | 
|  | @ -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) | ||||
| 
 | ||||
| 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`. | ||||
|  | ||||
| 
 | ||||
| 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. | ||||
| See the [design | ||||
| document](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/design_ha.md) | ||||
| for implementation details. | ||||
| 
 | ||||
| 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. | ||||
| ## Setup Instructions | ||||
| 
 | ||||
| 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. | ||||
| If you [installed](https://about.gitlab.com/install/) GitLab using the Omnibus | ||||
| package (highly recommended), follow the steps below: | ||||
| 
 | ||||
| ### Setup | ||||
| 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) | ||||
| 
 | ||||
| In this setup guide we will start by configuring Praefect, then its child | ||||
| Gitaly nodes, and lastly the GitLab server configuration. | ||||
| ### Preparation | ||||
| 
 | ||||
| Before beginning, you should already have a working GitLab instance. [Learn how | ||||
| to install GitLab](https://about.gitlab.com/install/). | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| 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: | ||||
| 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. `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 | ||||
|    Create a new database `praefect_production`. By creating the database while | ||||
|    connected as the `praefect` user, we are confident they have access. | ||||
| 
 | ||||
| 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']`. | ||||
|    ```sql | ||||
|    CREATE DATABASE praefect_production WITH ENCODING=UTF8; | ||||
|    ``` | ||||
| 
 | ||||
| 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`. | ||||
| The database used by Praefect is now configured. | ||||
| 
 | ||||
| If you are using an uncrypted connection to Postgres, set `praefect['database_sslmode']` to false. | ||||
| ### Praefect | ||||
| 
 | ||||
| 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']`: | ||||
| To complete this section you will need: | ||||
| 
 | ||||
| ```ruby | ||||
| # /etc/gitlab/gitlab.rb on praefect server | ||||
| - [Configured PostgreSQL server](#postgresql), including: | ||||
|   - IP/host address (`POSTGRESQL_SERVER_ADDRESS`) | ||||
|   - password (`PRAEFECT_SQL_PASSWORD`) | ||||
| 
 | ||||
| # 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 | ||||
| Praefect should be run on a dedicated node. Do not run Praefect on the | ||||
| application server, or a Gitaly node. | ||||
| 
 | ||||
| # Prevent database connections during 'gitlab-ctl reconfigure' | ||||
| gitlab_rails['rake_cache_clear'] = false | ||||
| gitlab_rails['auto_migrate'] = false | ||||
| 1. SSH into the **Praefect** node and login as root: | ||||
| 
 | ||||
| praefect['enable'] = true | ||||
|    ```shell | ||||
|    sudo -i | ||||
|    ``` | ||||
| 
 | ||||
| # 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' | ||||
| 1. Disable all other services by editing `/etc/gitlab/gitlab.rb`: | ||||
| 
 | ||||
| # Replace PRAEFECT_EXTERNAL_TOKEN with a real secret | ||||
| praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN' | ||||
|    ```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 | ||||
| 
 | ||||
| # 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' | ||||
|     } | ||||
|   } | ||||
| } | ||||
|    # Enable only the Praefect service | ||||
|    praefect['enable'] = true | ||||
| 
 | ||||
| # 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' | ||||
|    # Prevent database connections during 'gitlab-ctl reconfigure' | ||||
|    gitlab_rails['rake_cache_clear'] = false | ||||
|    gitlab_rails['auto_migrate'] = false | ||||
|    ``` | ||||
| 
 | ||||
| # Uncomment the line below if you do not want to use an encrypted | ||||
| # connection to PostgreSQL | ||||
| # praefect['database_sslmode'] = 'disable' | ||||
| 1. Configure **Praefect** to listen on network interfaces by editing | ||||
|    `/etc/gitlab/gitlab.rb`: | ||||
| 
 | ||||
| # 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' | ||||
|    ```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' | ||||
|    ``` | ||||
| 
 | ||||
| # Uncomment and modify this line if your PostgreSQL server uses a custom | ||||
| # CA | ||||
| # praefect['database_sslrootcert'] = '/path/to/rootcert' | ||||
| ``` | ||||
| 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 : | ||||
| 
 | ||||
| Replace `POSTGRESQL_SERVER_ADDRESS`, `PRAEFECT_EXTERNAL_TOKEN`, `PRAEFECT_INTERNAL_TOKEN`, | ||||
| and `PRAEFECT_SQL_PASSWORD` with their respective values. | ||||
|    ```ruby | ||||
|    praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN' | ||||
|    ``` | ||||
| 
 | ||||
| Save the file and reconfigure Praefect: | ||||
| 1. Configure **Praefect** to connect to the PostgreSQL database by editing | ||||
|    `/etc/gitlab/gitlab.rb`. | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-ctl reconfigure | ||||
| ``` | ||||
|    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. | ||||
| 
 | ||||
| After you reconfigure, verify that Praefect can reach PostgreSQL: | ||||
|    ```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' | ||||
|    ``` | ||||
| 
 | ||||
| ```shell | ||||
| sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping | ||||
| ``` | ||||
|    If you want to use a TLS client certificate, the options below can be used: | ||||
| 
 | ||||
| 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. | ||||
|    ```ruby | ||||
|    # Connect to PostreSQL using a TLS client certificate | ||||
|    # praefect['database_sslcert'] = '/path/to/client-cert' | ||||
|    # praefect['database_sslkey'] = '/path/to/client-key' | ||||
| 
 | ||||
| #### Gitaly | ||||
|    # Trust a custom certificate authority | ||||
|    # praefect['database_sslrootcert'] = '/path/to/rootcert' | ||||
|    ``` | ||||
| 
 | ||||
| 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. | ||||
|    By default Praefect will refuse to make an unencrypted connection to | ||||
|    PostgreSQL. You can override this by uncommenting the following line: | ||||
| 
 | ||||
| Below is an example configuration for `gitaly-1`, the only difference for the | ||||
| other Gitaly nodes is the storage name under `git_data_dirs`. | ||||
|    ```ruby | ||||
|    # praefect['database_sslmode'] = 'disable' | ||||
|    ``` | ||||
| 
 | ||||
| Note that `gitaly['auth_token']` matches the `token` value listed under `praefect['virtual_storages']` | ||||
| on the Praefect node. | ||||
| 1. Configure the **Praefect** cluster to connect to each Gitaly node in the | ||||
|    cluster by editing `/etc/gitlab/gitlab.rb`. | ||||
| 
 | ||||
| ```ruby | ||||
| # /etc/gitlab/gitlab.rb on gitaly node inside praefect cluster | ||||
|    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. | ||||
| 
 | ||||
| # 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 | ||||
|    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`. | ||||
| 
 | ||||
| # Prevent database connections during 'gitlab-ctl reconfigure' | ||||
| gitlab_rails['rake_cache_clear'] = false | ||||
| gitlab_rails['auto_migrate'] = false | ||||
|    Replace `GITALY_HOST` with the IP/host address of the each Gitaly node. | ||||
| 
 | ||||
| # Replace GITLAB_SHELL_SECRET_TOKEN below with real secret | ||||
| gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN' | ||||
|    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. | ||||
| 
 | ||||
| # 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' | ||||
|    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. | ||||
| 
 | ||||
| # Replace PRAEFECT_INTERNAL_TOKEN below with a real secret. | ||||
| gitaly['auth_token'] = 'PRAEFECT_INTERNAL_TOKEN' | ||||
|    ```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' | ||||
|        } | ||||
|      } | ||||
|    } | ||||
|    ``` | ||||
| 
 | ||||
| # 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" | ||||
| 1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure): | ||||
| 
 | ||||
| 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" | ||||
|   } | ||||
| }) | ||||
| ``` | ||||
|    ```shell | ||||
|    sudo gitlab-ctl reconfigure | ||||
|    ``` | ||||
| 
 | ||||
| Replace `GITLAB_SHELL_SECRET_TOKEN` and `PRAEFECT_INTERNAL_TOKEN` | ||||
| with their respective values. | ||||
| 1. Verify that Praefect can reach PostgreSQL: | ||||
| 
 | ||||
| For more information on Gitaly server configuration, see our [Gitaly documentation](index.md#3-gitaly-server-configuration). | ||||
|    ```shell | ||||
|    sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping | ||||
|    ``` | ||||
| 
 | ||||
| When finished editing the configuration file for each Gitaly server, run the | ||||
| reconfigure command to put changes into effect: | ||||
|    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. | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-ctl reconfigure | ||||
| ``` | ||||
| ### Gitaly | ||||
| 
 | ||||
| When all Gitaly servers are configured, you can run the Praefect connection | ||||
| 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. | ||||
| 
 | ||||
| 1. SSH into the **Praefect** node and run the Praefect connection checker: | ||||
| 
 | ||||
|    ```shell | ||||
|    sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes | ||||
|    ``` | ||||
| 
 | ||||
| ### GitLab | ||||
| 
 | ||||
| To complete this section you will need: | ||||
| 
 | ||||
| - [Configured Praefect node](#praefect) | ||||
| - [Configured Gitaly nodes](#gitaly) | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
|    ```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' | ||||
|      } | ||||
|    }) | ||||
|    ``` | ||||
| 
 | ||||
| 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 | ||||
| sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes | ||||
| curl --request PUT \ | ||||
|   --header "PRIVATE-TOKEN: <your_access_token>" \ | ||||
|   --data "repository_storage=praefect" \ | ||||
|   https://example.gitlab.com/api/v4/projects/123 | ||||
| ``` | ||||
| 
 | ||||
| #### 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>" | ||||
| ``` | ||||
| 
 | ||||
| Replace `GITLAB_SHELL_SECRET_TOKEN` and `PRAEFECT_EXTERNAL_TOKEN` | ||||
| with their respective values. | ||||
| 
 | ||||
| Note that the storage name used is the same as the `praefect['virtual_storage_name']` set | ||||
| on the Praefect node. | ||||
| 
 | ||||
| Save your changes and reconfigure GitLab: | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-ctl reconfigure | ||||
| ``` | ||||
| 
 | ||||
| Run `sudo gitlab-rake gitlab:gitaly:check` to confirm that GitLab can reach Praefect. | ||||
| 
 | ||||
| ### Testing Praefect | ||||
| 
 | ||||
| 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`. | ||||
| 
 | ||||
|  |  | |||
|  | @ -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" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  |  | |||
|  | @ -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. | | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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' | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 } | ||||
|  |  | |||
|  | @ -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 }] | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue