Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									63b3a14f15
								
							
						
					
					
						commit
						b3930fc34f
					
				
							
								
								
									
										2
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										2
									
								
								Gemfile
								
								
								
								
							|  | @ -160,7 +160,7 @@ gem 'wikicloth', '0.8.1' | |||
| gem 'asciidoctor', '~> 2.0.10' | ||||
| gem 'asciidoctor-include-ext', '~> 0.3.1', require: false | ||||
| gem 'asciidoctor-plantuml', '~> 0.0.12' | ||||
| gem 'asciidoctor-kroki', '~> 0.3.0', require: false | ||||
| gem 'asciidoctor-kroki', '~> 0.4.0', require: false | ||||
| gem 'rouge', '~> 3.26.0' | ||||
| gem 'truncato', '~> 0.7.11' | ||||
| gem 'bootstrap_form', '~> 4.2.0' | ||||
|  |  | |||
|  | @ -84,7 +84,7 @@ GEM | |||
|     asciidoctor (2.0.12) | ||||
|     asciidoctor-include-ext (0.3.1) | ||||
|       asciidoctor (>= 1.5.6, < 3.0.0) | ||||
|     asciidoctor-kroki (0.3.0) | ||||
|     asciidoctor-kroki (0.4.0) | ||||
|       asciidoctor (~> 2.0) | ||||
|     asciidoctor-plantuml (0.0.12) | ||||
|       asciidoctor (>= 1.5.6, < 3.0.0) | ||||
|  | @ -1334,7 +1334,7 @@ DEPENDENCIES | |||
|   asana (~> 0.10.3) | ||||
|   asciidoctor (~> 2.0.10) | ||||
|   asciidoctor-include-ext (~> 0.3.1) | ||||
|   asciidoctor-kroki (~> 0.3.0) | ||||
|   asciidoctor-kroki (~> 0.4.0) | ||||
|   asciidoctor-plantuml (~> 0.0.12) | ||||
|   atlassian-jwt (~> 0.2.0) | ||||
|   attr_encrypted (~> 3.1.0) | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| import { | ||||
|   GlDropdown, | ||||
|   GlDropdownDivider, | ||||
|   GlDropdownSectionHeader, | ||||
|   GlSearchBoxByType, | ||||
|   GlSprintf, | ||||
|   GlIcon, | ||||
|  | @ -27,7 +26,6 @@ export default { | |||
|   components: { | ||||
|     GlDropdown, | ||||
|     GlDropdownDivider, | ||||
|     GlDropdownSectionHeader, | ||||
|     GlSearchBoxByType, | ||||
|     GlSprintf, | ||||
|     GlIcon, | ||||
|  | @ -161,8 +159,13 @@ export default { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <gl-dropdown v-bind="$attrs" class="ref-selector" @shown="focusSearchBox"> | ||||
|     <template slot="button-content"> | ||||
|   <gl-dropdown | ||||
|     v-bind="$attrs" | ||||
|     :header-text="i18n.dropdownHeader" | ||||
|     class="ref-selector" | ||||
|     @shown="focusSearchBox" | ||||
|   > | ||||
|     <template #button-content> | ||||
|       <span class="gl-flex-grow-1 gl-ml-2 gl-text-gray-400" data-testid="button-content"> | ||||
|         <span v-if="selectedRef" class="gl-font-monospace">{{ selectedRef }}</span> | ||||
|         <span v-else>{{ i18n.noRefSelected }}</span> | ||||
|  | @ -170,13 +173,7 @@ export default { | |||
|       <gl-icon name="chevron-down" /> | ||||
|     </template> | ||||
| 
 | ||||
|     <div class="gl-display-flex gl-flex-direction-column ref-selector-dropdown-content"> | ||||
|       <gl-dropdown-section-header> | ||||
|         <span class="gl-text-center gl-display-block">{{ i18n.dropdownHeader }}</span> | ||||
|       </gl-dropdown-section-header> | ||||
| 
 | ||||
|       <gl-dropdown-divider /> | ||||
| 
 | ||||
|     <template #header> | ||||
|       <gl-search-box-by-type | ||||
|         ref="searchBox" | ||||
|         v-model.trim="query" | ||||
|  | @ -184,72 +181,66 @@ export default { | |||
|         @input="onSearchBoxInput" | ||||
|         @keydown.enter.prevent="onSearchBoxEnter" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|       <div class="gl-flex-grow-1 gl-overflow-y-auto"> | ||||
|         <gl-loading-icon v-if="isLoading" size="lg" class="gl-my-3" /> | ||||
|     <gl-loading-icon v-if="isLoading" size="lg" class="gl-my-3" /> | ||||
| 
 | ||||
|         <div | ||||
|           v-else-if="showNoResults" | ||||
|           class="gl-text-center gl-mx-3 gl-py-3" | ||||
|           data-testid="no-results" | ||||
|         > | ||||
|           <gl-sprintf v-if="lastQuery" :message="i18n.noResultsWithQuery"> | ||||
|             <template #query> | ||||
|               <b class="gl-word-break-all">{{ lastQuery }}</b> | ||||
|             </template> | ||||
|           </gl-sprintf> | ||||
| 
 | ||||
|           <span v-else>{{ i18n.noResults }}</span> | ||||
|         </div> | ||||
| 
 | ||||
|         <template v-else> | ||||
|           <template v-if="showBranchesSection"> | ||||
|             <ref-results-section | ||||
|               :section-title="i18n.branches" | ||||
|               :total-count="matches.branches.totalCount" | ||||
|               :items="matches.branches.list" | ||||
|               :selected-ref="selectedRef" | ||||
|               :error="matches.branches.error" | ||||
|               :error-message="i18n.branchesErrorMessage" | ||||
|               :show-header="showSectionHeaders" | ||||
|               data-testid="branches-section" | ||||
|               @selected="selectRef($event)" | ||||
|             /> | ||||
| 
 | ||||
|             <gl-dropdown-divider v-if="showTagsSection || showCommitsSection" /> | ||||
|           </template> | ||||
| 
 | ||||
|           <template v-if="showTagsSection"> | ||||
|             <ref-results-section | ||||
|               :section-title="i18n.tags" | ||||
|               :total-count="matches.tags.totalCount" | ||||
|               :items="matches.tags.list" | ||||
|               :selected-ref="selectedRef" | ||||
|               :error="matches.tags.error" | ||||
|               :error-message="i18n.tagsErrorMessage" | ||||
|               :show-header="showSectionHeaders" | ||||
|               data-testid="tags-section" | ||||
|               @selected="selectRef($event)" | ||||
|             /> | ||||
| 
 | ||||
|             <gl-dropdown-divider v-if="showCommitsSection" /> | ||||
|           </template> | ||||
| 
 | ||||
|           <template v-if="showCommitsSection"> | ||||
|             <ref-results-section | ||||
|               :section-title="i18n.commits" | ||||
|               :total-count="matches.commits.totalCount" | ||||
|               :items="matches.commits.list" | ||||
|               :selected-ref="selectedRef" | ||||
|               :error="matches.commits.error" | ||||
|               :error-message="i18n.commitsErrorMessage" | ||||
|               :show-header="showSectionHeaders" | ||||
|               data-testid="commits-section" | ||||
|               @selected="selectRef($event)" | ||||
|             /> | ||||
|           </template> | ||||
|     <div v-else-if="showNoResults" class="gl-text-center gl-mx-3 gl-py-3" data-testid="no-results"> | ||||
|       <gl-sprintf v-if="lastQuery" :message="i18n.noResultsWithQuery"> | ||||
|         <template #query> | ||||
|           <b class="gl-word-break-all">{{ lastQuery }}</b> | ||||
|         </template> | ||||
|       </div> | ||||
|       </gl-sprintf> | ||||
| 
 | ||||
|       <span v-else>{{ i18n.noResults }}</span> | ||||
|     </div> | ||||
| 
 | ||||
|     <template v-else> | ||||
|       <template v-if="showBranchesSection"> | ||||
|         <ref-results-section | ||||
|           :section-title="i18n.branches" | ||||
|           :total-count="matches.branches.totalCount" | ||||
|           :items="matches.branches.list" | ||||
|           :selected-ref="selectedRef" | ||||
|           :error="matches.branches.error" | ||||
|           :error-message="i18n.branchesErrorMessage" | ||||
|           :show-header="showSectionHeaders" | ||||
|           data-testid="branches-section" | ||||
|           @selected="selectRef($event)" | ||||
|         /> | ||||
| 
 | ||||
|         <gl-dropdown-divider v-if="showTagsSection || showCommitsSection" /> | ||||
|       </template> | ||||
| 
 | ||||
|       <template v-if="showTagsSection"> | ||||
|         <ref-results-section | ||||
|           :section-title="i18n.tags" | ||||
|           :total-count="matches.tags.totalCount" | ||||
|           :items="matches.tags.list" | ||||
|           :selected-ref="selectedRef" | ||||
|           :error="matches.tags.error" | ||||
|           :error-message="i18n.tagsErrorMessage" | ||||
|           :show-header="showSectionHeaders" | ||||
|           data-testid="tags-section" | ||||
|           @selected="selectRef($event)" | ||||
|         /> | ||||
| 
 | ||||
|         <gl-dropdown-divider v-if="showCommitsSection" /> | ||||
|       </template> | ||||
| 
 | ||||
|       <template v-if="showCommitsSection"> | ||||
|         <ref-results-section | ||||
|           :section-title="i18n.commits" | ||||
|           :total-count="matches.commits.totalCount" | ||||
|           :items="matches.commits.list" | ||||
|           :selected-ref="selectedRef" | ||||
|           :error="matches.commits.error" | ||||
|           :error-message="i18n.commitsErrorMessage" | ||||
|           :show-header="showSectionHeaders" | ||||
|           data-testid="commits-section" | ||||
|           @selected="selectRef($event)" | ||||
|         /> | ||||
|       </template> | ||||
|     </template> | ||||
|   </gl-dropdown> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,13 +1,4 @@ | |||
| .ref-selector { | ||||
|   & &-dropdown-content { | ||||
|     // Setting a max height is necessary to allow the dropdown's content | ||||
|     // to control where and how scrollbars appear. | ||||
|     // This content is limited to the max-height of the dropdown | ||||
|     // ($dropdown-max-height-lg) minus the additional padding | ||||
|     // on the top and bottom (2 * $gl-padding-8) | ||||
|     max-height: $dropdown-max-height-lg - 2 * $gl-padding-8; | ||||
|   } | ||||
| 
 | ||||
|   .dropdown-menu.show { | ||||
|     // Make the dropdown a little wider and longer than usual | ||||
|     // since it contains quite a bit of content. | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| module Security | ||||
|   class LicenseComplianceJobsFinder < JobsFinder | ||||
|     def self.allowed_job_types | ||||
|       [:license_management, :license_scanning] | ||||
|       [:license_scanning] | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -17,15 +17,21 @@ module Resolvers | |||
|             default_value: nil, | ||||
|             description: 'Sort projects by this criteria.' | ||||
| 
 | ||||
|     argument :ids, [GraphQL::ID_TYPE], | ||||
|              required: false, | ||||
|              default_value: nil, | ||||
|              description: 'Filter projects by IDs.' | ||||
| 
 | ||||
|     type Types::ProjectType, null: true | ||||
| 
 | ||||
|     def resolve(include_subgroups:, sort:, search:) | ||||
|     def resolve(include_subgroups:, sort:, search:, ids:) | ||||
|       # The namespace could have been loaded in batch by `BatchLoader`. | ||||
|       # At this point we need the `id` or the `full_path` of the namespace | ||||
|       # to query for projects, so make sure it's loaded and not `nil` before continuing. | ||||
|       return Project.none if namespace.nil? | ||||
| 
 | ||||
|       query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route | ||||
|       query = ids ? query.merge(Project.where(id: parse_gids(ids))) : query # rubocop: disable CodeReuse/ActiveRecord | ||||
| 
 | ||||
|       return query unless search.present? | ||||
| 
 | ||||
|  | @ -48,6 +54,10 @@ module Resolvers | |||
|         object.respond_to?(:sync) ? object.sync : object | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def parse_gids(gids) | ||||
|       gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id } | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ module Users | |||
|     # remove - The IDs of the authorization rows to remove. | ||||
|     # add - Rows to insert in the form `[user id, project id, access level]` | ||||
|     def update_authorizations(remove = [], add = []) | ||||
|       log_refresh_details(remove.length, add.length) | ||||
|       log_refresh_details(remove, add) | ||||
| 
 | ||||
|       User.transaction do | ||||
|         user.remove_project_authorizations(remove) unless remove.empty? | ||||
|  | @ -104,11 +104,16 @@ module Users | |||
|       user.reset | ||||
|     end | ||||
| 
 | ||||
|     def log_refresh_details(rows_deleted, rows_added) | ||||
|     def log_refresh_details(remove, add) | ||||
|       Gitlab::AppJsonLogger.info(event: 'authorized_projects_refresh', | ||||
|                                  user_id: user.id, | ||||
|                                  'authorized_projects_refresh.source': source, | ||||
|                                  'authorized_projects_refresh.rows_deleted': rows_deleted, | ||||
|                                  'authorized_projects_refresh.rows_added': rows_added) | ||||
|                                  'authorized_projects_refresh.rows_deleted_count': remove.length, | ||||
|                                  'authorized_projects_refresh.rows_added_count': add.length, | ||||
|                                  # most often there's only a few entries in remove and add, but limit it to the first 5 | ||||
|                                  # entries to avoid flooding the logs | ||||
|                                  'authorized_projects_refresh.rows_deleted_slice': remove.first(5), | ||||
|                                  'authorized_projects_refresh.rows_added_slice': add.first(5)) | ||||
|     end | ||||
| 
 | ||||
|     def fresh_access_levels_per_project | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Update Kroki to fix Wavedrom graphs | ||||
| merge_request: 55659 | ||||
| author: | ||||
| type: fixed | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Query group projects by ids with GraphQL | ||||
| merge_request: 55383 | ||||
| author: | ||||
| type: added | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| title: Small visual updates to Git ref selector dropdown on New/Edit Release page | ||||
| merge_request: 55121 | ||||
| author: | ||||
| type: changed | ||||
|  | @ -1,5 +0,0 @@ | |||
| --- | ||||
| title: Add rake task to cleanup description templates cache in batches | ||||
| merge_request: 54706 | ||||
| author: | ||||
| type: added | ||||
|  | @ -1,5 +0,0 @@ | |||
| --- | ||||
| title: Do not expose user name if user is project bot | ||||
| merge_request: 54022 | ||||
| author: | ||||
| type: changed | ||||
|  | @ -13,13 +13,13 @@ GitLab provides Rake tasks for general maintenance. | |||
| This command gathers information about your GitLab installation and the system it runs on. | ||||
| These may be useful when asking for help or reporting issues. | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake gitlab:env:info | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| bundle exec rake gitlab:env:info RAILS_ENV=production | ||||
|  | @ -76,13 +76,13 @@ installations: a license cannot be installed into GitLab Community Edition. | |||
| These may be useful when raising tickets with Support, or for programmatically | ||||
| checking your license parameters. | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake gitlab:license:info | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| bundle exec rake gitlab:license:info RAILS_ENV=production | ||||
|  | @ -119,13 +119,13 @@ You may also have a look at our troubleshooting guides for: | |||
| 
 | ||||
| To run `gitlab:check`, run: | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake gitlab:check | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| bundle exec rake gitlab:check RAILS_ENV=production | ||||
|  | @ -182,13 +182,13 @@ Checking GitLab ... Finished | |||
| 
 | ||||
| In some case it is necessary to rebuild the `authorized_keys` file. To do this, run: | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake gitlab:shell:setup | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
|  | @ -203,64 +203,18 @@ You will lose any data stored in authorized_keys file. | |||
| Do you want to continue (yes/no)? yes | ||||
| ``` | ||||
| 
 | ||||
| ## Clear issue and merge request description template names cache | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54706) in GitLab 13.10. | ||||
| 
 | ||||
| If the issue or merge request description template names in the dropdown | ||||
| do not reflect the actual description template names in the repository, consider clearing | ||||
| the Redis cache that stores the template names information. | ||||
| You can clear the cache of | ||||
| [all issues and merge request templates in the installation](#clear-cache-for-all-issue-and-merge-request-template-names) | ||||
| or [in a specific project](#clear-cache-for-issue-and-merge-request-template-names-in-specific-projects). | ||||
| 
 | ||||
| ### Clear cache for all issue and merge request template names | ||||
| 
 | ||||
| If you want to refresh issue and merge request templates for all projects: | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake cache:clear:description_templates | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
| sudo -u git -H bundle exec rake cache:clear:description_templates RAILS_ENV=production | ||||
| ``` | ||||
| 
 | ||||
| ### Clear cache for issue and merge request template names in specific projects | ||||
| 
 | ||||
| If you want to refresh issue and merge request templates for specific projects, | ||||
| provide a comma-separated list of IDs as the `project_ids` parameter to the Rake task. | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake cache:clear:description_templates project_ids=10,25,35 | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
| sudo -u git -H bundle exec rake cache:clear:description_templates project_ids=10,25,35 RAILS_ENV=production | ||||
| ``` | ||||
| 
 | ||||
| ## Clear Redis cache | ||||
| 
 | ||||
| If for some reason the dashboard displays the wrong information, you might want to | ||||
| clear Redis' cache. To do this, run: | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake cache:clear | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
|  | @ -275,7 +229,7 @@ missing some icons. In that case, try to precompile the assets again. | |||
| This only applies to source installations and does NOT apply to | ||||
| Omnibus packages. | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
|  | @ -295,13 +249,13 @@ Sometimes you need to know if your GitLab installation can connect to a TCP | |||
| service on another machine - perhaps a PostgreSQL or HTTPS server. A Rake task | ||||
| is included to help you with this: | ||||
| 
 | ||||
| **For Omnibus installations** | ||||
| **Omnibus Installation** | ||||
| 
 | ||||
| ```shell | ||||
| sudo gitlab-rake gitlab:tcp_check[example.com,80] | ||||
| ``` | ||||
| 
 | ||||
| **For installations from source** | ||||
| **Source Installation** | ||||
| 
 | ||||
| ```shell | ||||
| cd /home/git/gitlab | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ GET /projects/:id/deployments | |||
| | `sort`           | string         | no       | Return deployments sorted in `asc` or `desc` order. Default is `asc`                                            | | ||||
| | `updated_after`  | datetime       | no       | Return deployments updated after the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | | ||||
| | `updated_before` | datetime       | no       | Return deployments updated before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | | ||||
| | `environment`    | string         | no       | The [name of the environment](../ci/environments/index.md#defining-environments) to filter deployments by       | | ||||
| | `environment`    | string         | no       | The [name of the environment](../ci/environments/index.md) to filter deployments by       | | ||||
| | `status`         | string         | no       | The status to filter deployments by                                                                             | | ||||
| 
 | ||||
| The status attribute can be one of the following values: | ||||
|  | @ -281,7 +281,7 @@ POST /projects/:id/deployments | |||
| | Attribute     | Type           | Required | Description                                                                                                     | | ||||
| |---------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------| | ||||
| | `id`          | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | ||||
| | `environment` | string         | yes      | The [name of the environment](../ci/environments/index.md#defining-environments) to create the deployment for   | | ||||
| | `environment` | string         | yes      | The [name of the environment](../ci/environments/index.md) to create the deployment for                         | | ||||
| | `sha`         | string         | yes      | The SHA of the commit that is deployed                                                                          | | ||||
| | `ref`         | string         | yes      | The name of the branch or tag that is deployed                                                                  | | ||||
| | `tag`         | boolean        | yes      | A boolean that indicates if the deployed ref is a tag (true) or not (false)                                     | | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 58 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 58 KiB | 
|  | @ -25,295 +25,73 @@ If you have a deployment service like [Kubernetes](../../user/project/clusters/i | |||
| associated with your project, you can use it to assist with your deployments. | ||||
| You can even access a [web terminal](#web-terminals) for your environment from within GitLab. | ||||
| 
 | ||||
| ## Configuring environments | ||||
| ## View environments and deployments | ||||
| 
 | ||||
| Configuring environments involves: | ||||
| Prerequisites: | ||||
| 
 | ||||
| 1. Understanding how [pipelines](../pipelines/index.md) work. | ||||
| 1. Defining environments in your project's [`.gitlab-ci.yml`](../yaml/README.md) file. | ||||
| 1. Creating a job configured to deploy your application. For example, a deploy job configured with [`environment`](../yaml/README.md#environment) to deploy your application to a [Kubernetes cluster](../../user/project/clusters/index.md). | ||||
| - You must have a minimum of [Reporter permission](../../user/permissions.md#project-members-permissions). | ||||
| 
 | ||||
| The rest of this section illustrates how to configure environments and deployments using | ||||
| an example scenario. It assumes you have already: | ||||
| To view a list of environments and deployments: | ||||
| 
 | ||||
| - Created a [project](../../user/project/working_with_projects.md#create-a-project) in GitLab. | ||||
| - Set up [a runner](../runners/README.md). | ||||
| 1. Go to the project's **Operations > Environments** page. | ||||
|    The environments are displayed. | ||||
| 
 | ||||
| In the scenario: | ||||
|     | ||||
| 
 | ||||
| - We are developing an application. | ||||
| - We want to run tests and build our app on all branches. | ||||
| - Our default branch is `master`. | ||||
| - We deploy the app only when a pipeline on `master` branch is run. | ||||
| 1. To view a list of deployments for an environment, select the environment name, | ||||
|    for example, `staging`. | ||||
| 
 | ||||
| ### Defining environments | ||||
|     | ||||
| 
 | ||||
| You can create environments manually in the web interface, but we recommend | ||||
| that you define your environments in the `.gitlab-ci.yml` file. After the first | ||||
| deploy, the environments are automatically created. | ||||
| Deployments show up in this list only after a deployment job has created them. | ||||
| 
 | ||||
| Let's consider the following `.gitlab-ci.yml` example: | ||||
| ## Types of environments | ||||
| 
 | ||||
| ```yaml | ||||
| stages: | ||||
|   - test | ||||
|   - build | ||||
|   - deploy | ||||
| There are two types of environments: | ||||
| 
 | ||||
| test: | ||||
|   stage: test | ||||
|   script: echo "Running tests" | ||||
| - Static environments have static names, like `staging` or `production`. | ||||
| - Dynamic environments have dynamic names. Dynamic environments | ||||
|   are a fundamental part of [Review apps](../review_apps/index.md). | ||||
| 
 | ||||
| build: | ||||
|   stage: build | ||||
|   script: echo "Building the app" | ||||
| ### Create a static environment | ||||
| 
 | ||||
| deploy_staging: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to staging server" | ||||
|   environment: | ||||
|     name: staging | ||||
|     url: https://staging.example.com | ||||
|   only: | ||||
|     - master | ||||
| ``` | ||||
| You can create an environment and deployment in the UI or in your `.gitlab-ci.yml` file. | ||||
| 
 | ||||
| We have defined three [stages](../yaml/README.md#stages): | ||||
| In the UI: | ||||
| 
 | ||||
| - `test` | ||||
| - `build` | ||||
| - `deploy` | ||||
| 1. Go to the project's **Operations > Environments** page. | ||||
| 1. Select **New environment**. | ||||
| 1. Enter a name and external URL. | ||||
| 1. Select **Save**. | ||||
| 
 | ||||
| The jobs assigned to these stages run in this order. If any job fails, then | ||||
| the pipeline fails and jobs that are assigned to the next stage don't run. | ||||
| In your `.gitlab-ci.yml` file: | ||||
| 
 | ||||
| In our case: | ||||
| 1. Specify a name for the environment and optionally, a URL, which determines the deployment URL. | ||||
|    For example: | ||||
| 
 | ||||
| - The `test` job runs first. | ||||
| - Then the `build` job. | ||||
| - Lastly the `deploy_staging` job. | ||||
|    ```yaml | ||||
|    deploy_staging: | ||||
|      stage: deploy | ||||
|      script: | ||||
|        - echo "Deploy to staging server" | ||||
|      environment: | ||||
|        name: staging | ||||
|        url: https://staging.example.com | ||||
|    ``` | ||||
| 
 | ||||
| With this configuration, we: | ||||
| 1. Trigger a deployment. (For example, by creating and pushing a commit.) | ||||
| 
 | ||||
| - Check that the tests pass. | ||||
| - Ensure that our app is able to be built successfully. | ||||
| - Lastly we deploy to the staging server. | ||||
| When the job runs, the environment and deployment are created. | ||||
| 
 | ||||
| Note that the `environment` keyword defines where the app is deployed. The environment `name` and | ||||
| `url` is exposed in various places within GitLab. Each time a job that has an environment specified | ||||
| succeeds, a deployment is recorded along with the Git SHA and environment name. | ||||
| NOTE: | ||||
| Some characters cannot be used in environment names. | ||||
| For more information about the `environment` keywords, see | ||||
| [the `.gitlab-ci.yml` keyword reference](../yaml/README.md#environment). | ||||
| 
 | ||||
| WARNING: | ||||
| Some characters are not allowed in environment names. Use only letters, | ||||
| numbers, spaces, and `-`, `_`, `/`, `{`, `}`, or `.`. Also, it must not start nor end with `/`. | ||||
| ### Create a dynamic environment | ||||
| 
 | ||||
| In summary, with the above `.gitlab-ci.yml` we have achieved the following: | ||||
| 
 | ||||
| - All branches run the `test` and `build` jobs. | ||||
| - The `deploy_staging` job runs [only](../yaml/README.md#onlyexcept-basic) on the `master` | ||||
|   branch, which means all merge requests that are created from branches don't | ||||
|   get deployed to the staging server. | ||||
| - When a merge request is merged, all jobs run and the `deploy_staging` | ||||
|   job deploys our code to a staging server while the deployment | ||||
|   is recorded in an environment named `staging`. | ||||
| 
 | ||||
| #### CI/CD variables and runners | ||||
| 
 | ||||
| Starting with GitLab 8.15, the environment name is exposed to the runner in | ||||
| two forms: | ||||
| 
 | ||||
| - `$CI_ENVIRONMENT_NAME`. The name given in `.gitlab-ci.yml` (with any CI/CD variables | ||||
|   expanded). | ||||
| - `$CI_ENVIRONMENT_SLUG`. A "cleaned-up" version of the name, suitable for use in URLs, | ||||
|   DNS, etc. | ||||
| 
 | ||||
| If you change the name of an existing environment, the: | ||||
| 
 | ||||
| - `$CI_ENVIRONMENT_NAME` variable is updated with the new environment name. | ||||
| - `$CI_ENVIRONMENT_SLUG` variable remains unchanged to prevent unintended side | ||||
|   effects. | ||||
| 
 | ||||
| Starting with GitLab 9.3, the environment URL is exposed to the runner via | ||||
| `$CI_ENVIRONMENT_URL`. The URL is expanded from either: | ||||
| 
 | ||||
| - `.gitlab-ci.yml`. | ||||
| - The external URL from the environment if not defined in `.gitlab-ci.yml`. | ||||
| 
 | ||||
| #### Set dynamic environment URLs after a job finishes | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17066) in GitLab 12.9. | ||||
| 
 | ||||
| In a job script, you can specify a static [environment URL](#using-the-environment-url). | ||||
| However, there may be times when you want a dynamic URL. For example, | ||||
| if you deploy a Review App to an external hosting | ||||
| service that generates a random URL per deployment, like `https://94dd65b.amazonaws.com/qa-lambda-1234567`, | ||||
| you don't know the URL before the deployment script finishes. | ||||
| If you want to use the environment URL in GitLab, you would have to update it manually. | ||||
| 
 | ||||
| To address this problem, you can configure a deployment job to report back a set of | ||||
| variables, including the URL that was dynamically-generated by the external service. | ||||
| GitLab supports the [dotenv (`.env`)](https://github.com/bkeepers/dotenv) file format, | ||||
| and expands the `environment:url` value with variables defined in the `.env` file. | ||||
| 
 | ||||
| To use this feature, specify the | ||||
| [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) keyword in `.gitlab-ci.yml`. | ||||
| 
 | ||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> | ||||
| For an overview, see [Set dynamic URLs after a job finished](https://youtu.be/70jDXtOf4Ig). | ||||
| 
 | ||||
| ##### Example of setting dynamic environment URLs | ||||
| 
 | ||||
| The following example shows a Review App that creates a new environment | ||||
| per merge request. The `review` job is triggered by every push, and | ||||
| creates or updates an environment named `review/your-branch-name`. | ||||
| The environment URL is set to `$DYNAMIC_ENVIRONMENT_URL`: | ||||
| 
 | ||||
| ```yaml | ||||
| review: | ||||
|   script: | ||||
|     - DYNAMIC_ENVIRONMENT_URL=$(deploy-script)                                 # In script, get the environment URL. | ||||
|     - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env    # Add the value to a dotenv file. | ||||
|   artifacts: | ||||
|     reports: | ||||
|       dotenv: deploy.env                                                       # Report back dotenv file to rails. | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_SLUG | ||||
|     url: $DYNAMIC_ENVIRONMENT_URL                                              # and set the variable produced in script to `environment:url` | ||||
|     on_stop: stop_review | ||||
| 
 | ||||
| stop_review: | ||||
|   script: | ||||
|     - ./teardown-environment | ||||
|   when: manual | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_SLUG | ||||
|     action: stop | ||||
| ``` | ||||
| 
 | ||||
| As soon as the `review` job finishes, GitLab updates the `review/your-branch-name` | ||||
| environment's URL. | ||||
| It parses the `deploy.env` report artifact, registers a list of variables as runtime-created, | ||||
| uses it for expanding `environment:url: $DYNAMIC_ENVIRONMENT_URL` and sets it to the environment URL. | ||||
| You can also specify a static part of the URL at `environment:url:`, such as | ||||
| `https://$DYNAMIC_ENVIRONMENT_URL`. If the value of `DYNAMIC_ENVIRONMENT_URL` is | ||||
| `example.com`, the final result is `https://example.com`. | ||||
| 
 | ||||
| The assigned URL for the `review/your-branch-name` environment is [visible in the UI](#using-the-environment-url). | ||||
| 
 | ||||
| Note the following: | ||||
| 
 | ||||
| - `stop_review` doesn't generate a dotenv report artifact, so it doesn't recognize the | ||||
|   `DYNAMIC_ENVIRONMENT_URL` environment variable. Therefore you shouldn't set `environment:url:` in the | ||||
|   `stop_review` job. | ||||
| - If the environment URL isn't valid (for example, the URL is malformed), the system doesn't update | ||||
|   the environment URL. | ||||
| - If the script that runs in `stop_review` exists only in your repository and therefore can't use | ||||
|   `GIT_STRATEGY: none`, configure [pipelines for merge requests](../../ci/merge_request_pipelines/index.md) | ||||
|   for these jobs. This ensures that runners can fetch the repository even after a feature branch is | ||||
|   deleted. For more information, see [Ref Specs for Runners](../pipelines/index.md#ref-specs-for-runners). | ||||
| 
 | ||||
| ### Configuring manual deployments | ||||
| 
 | ||||
| Adding `when: manual` to an automatically executed job's configuration converts it to | ||||
| a job requiring manual action. | ||||
| 
 | ||||
| To expand on the [previous example](#defining-environments), the following includes | ||||
| another job that deploys our app to a production server and is | ||||
| tracked by a `production` environment. | ||||
| 
 | ||||
| The `.gitlab-ci.yml` file for this is as follows: | ||||
| 
 | ||||
| ```yaml | ||||
| stages: | ||||
|   - test | ||||
|   - build | ||||
|   - deploy | ||||
| 
 | ||||
| test: | ||||
|   stage: test | ||||
|   script: echo "Running tests" | ||||
| 
 | ||||
| build: | ||||
|   stage: build | ||||
|   script: echo "Building the app" | ||||
| 
 | ||||
| deploy_staging: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to staging server" | ||||
|   environment: | ||||
|     name: staging | ||||
|     url: https://staging.example.com | ||||
|   only: | ||||
|     - master | ||||
| 
 | ||||
| deploy_prod: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to production server" | ||||
|   environment: | ||||
|     name: production | ||||
|     url: https://example.com | ||||
|   when: manual | ||||
|   only: | ||||
|     - master | ||||
| ``` | ||||
| 
 | ||||
| The `when: manual` action: | ||||
| 
 | ||||
| - Exposes a "play" button in the GitLab UI for that job. | ||||
| - Means the `deploy_prod` job is only triggered when the "play" button is clicked. | ||||
| 
 | ||||
| You can find the "play" button in the pipelines, environments, deployments, and jobs views. | ||||
| 
 | ||||
| | View            | Screenshot                                                                     | | ||||
| |:----------------|:-------------------------------------------------------------------------------| | ||||
| | Pipelines       |        | | ||||
| | Single pipeline |  | | ||||
| | Environments    |  | | ||||
| | Deployments     |    | | ||||
| | Jobs            |                | | ||||
| 
 | ||||
| Clicking the play button in any view triggers the `deploy_prod` job. The deployment is recorded as a | ||||
| new environment named `production`. | ||||
| 
 | ||||
| If your environment's name is `production` (all lowercase), it's recorded in | ||||
| [Value Stream Analytics](../../user/analytics/value_stream_analytics.md). | ||||
| 
 | ||||
| ### Configuring dynamic environments | ||||
| 
 | ||||
| Regular environments are good when deploying to "stable" environments like staging or production. | ||||
| 
 | ||||
| However, for environments for branches other than `master`, dynamic environments | ||||
| can be used. Dynamic environments make it possible to create environments on the fly by | ||||
| declaring their names dynamically in `.gitlab-ci.yml`. | ||||
| 
 | ||||
| Dynamic environments are a fundamental part of [Review apps](../review_apps/index.md). | ||||
| 
 | ||||
| #### Allowed variables | ||||
| 
 | ||||
| The `name` and `url` keywords for dynamic environments can use most available CI/CD variables, | ||||
| including: | ||||
| 
 | ||||
| - [Predefined CI/CD variables](../variables/README.md#predefined-cicd-variables) | ||||
| - [Project and group CI/CD variables](../variables/README.md) | ||||
| - [`.gitlab-ci.yml` CI/CD variables](../yaml/README.md#variables) | ||||
| 
 | ||||
| However, you cannot use variables defined: | ||||
| 
 | ||||
| - Under `script`. | ||||
| - On the runner's side. | ||||
| 
 | ||||
| There are also other variables that are unsupported in the context of `environment:name`. | ||||
| For more information, see [Where variables can be used](../variables/where_variables_can_be_used.md). | ||||
| 
 | ||||
| #### Example configuration | ||||
| 
 | ||||
| Runners expose various [predefined CI/CD variables](../variables/predefined_variables.md) when a job runs, so | ||||
| you can use them as environment names. | ||||
| 
 | ||||
| In the following example, the job deploys to all branches except `master`: | ||||
| To create a dynamic name and URL for an environment, you can use | ||||
| [predefined CI/CD variables](../variables/predefined_variables.md). For example: | ||||
| 
 | ||||
| ```yaml | ||||
| deploy_review: | ||||
|  | @ -331,37 +109,47 @@ deploy_review: | |||
| 
 | ||||
| In this example: | ||||
| 
 | ||||
| - The job's name is `deploy_review` and it runs on the `deploy` stage. | ||||
| - We set the `environment` with the `environment:name` as `review/$CI_COMMIT_REF_NAME`. | ||||
|   Since the [environment name](../yaml/README.md#environmentname) can contain slashes (`/`), we can | ||||
|   use this pattern to distinguish between dynamic and regular environments. | ||||
| - We tell the job to run [`only`](../yaml/README.md#onlyexcept-basic) on branches, | ||||
|   [`except`](../yaml/README.md#onlyexcept-basic) `master`. | ||||
| - The `name` is `review/$CI_COMMIT_REF_NAME`. Because the [environment name](../yaml/README.md#environmentname) | ||||
|   can contain slashes (`/`), you can use this pattern to distinguish between dynamic and static environments. | ||||
| - For the `url`, you could use `$CI_COMMIT_REF_NAME`, but because this value | ||||
|   may contain a `/` or other characters that would not be valid in a domain name or URL, | ||||
|   use `$CI_ENVIRONMENT_SLUG` instead. The `$CI_ENVIRONMENT_SLUG` variable is guaranteed to be unique. | ||||
| 
 | ||||
| For the value of: | ||||
| 
 | ||||
| - `environment:name`, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`, | ||||
|   which receives the value of the branch name. | ||||
| - `environment:url`, we want a specific and distinct URL for each branch. `$CI_COMMIT_REF_NAME` | ||||
|   may contain a `/` or other characters that would be invalid in a domain name or URL, | ||||
|   so we use `$CI_ENVIRONMENT_SLUG` to guarantee that we get a valid URL. | ||||
| 
 | ||||
|   For example, given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL is something | ||||
|   like `https://100-do-the-4f99a2.example.com`. Again, the way you set up | ||||
|   the web server to serve these requests is based on your setup. | ||||
| 
 | ||||
|   We have used `$CI_ENVIRONMENT_SLUG` here because it is guaranteed to be unique. If | ||||
|   you're using a workflow like [GitLab Flow](../../topics/gitlab_flow.md), collisions | ||||
|   are unlikely and you may prefer environment names to be more closely based on the | ||||
|   branch name. In that case, you could use `$CI_COMMIT_REF_NAME` in `environment:url` in | ||||
|   the example above: `https://$CI_COMMIT_REF_NAME.example.com`, which would give a URL | ||||
|   of `https://100-do-the-thing.example.com`. | ||||
| 
 | ||||
| You aren't required to use the same prefix or only slashes (`/`) in the dynamic environments' names. | ||||
| However, using this format enables the [grouping similar environments](#grouping-similar-environments) | ||||
| You do not have to use the same prefix or only slashes (`/`) in the dynamic environment name. | ||||
| However, when you use this format, you can use the [grouping similar environments](#grouping-similar-environments) | ||||
| feature. | ||||
| 
 | ||||
| ### Configuring Kubernetes deployments | ||||
| NOTE: | ||||
| Some variables cannot be used as environment names or URLs. | ||||
| For more information about the `environment` keywords, see | ||||
| [the `.gitlab-ci.yml` keyword reference](../yaml/README.md#environment). | ||||
| 
 | ||||
| ## Configure manual deployments | ||||
| 
 | ||||
| You can create a job that requires someone to manually start the deployment. | ||||
| For example: | ||||
| 
 | ||||
| ```yaml | ||||
| deploy_prod: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to production server" | ||||
|   environment: | ||||
|     name: production | ||||
|     url: https://example.com | ||||
|   when: manual | ||||
|   only: | ||||
|     - master | ||||
| ``` | ||||
| 
 | ||||
| The `when: manual` action: | ||||
| 
 | ||||
| - Exposes a play button for the job in the GitLab UI. | ||||
| - Means the `deploy_prod` job is only triggered when the play button is clicked. | ||||
| 
 | ||||
| You can find the play button in the pipelines, environments, deployments, and jobs views. | ||||
| 
 | ||||
| ## Configure Kubernetes deployments | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27630) in GitLab 12.6. | ||||
| 
 | ||||
|  | @ -402,132 +190,114 @@ trace on the deployment job page: | |||
| 
 | ||||
|  | ||||
| 
 | ||||
| #### Configuring incremental rollouts | ||||
| ### Configure incremental rollouts | ||||
| 
 | ||||
| Learn how to release production changes to only a portion of your Kubernetes pods with | ||||
| [incremental rollouts](../environments/incremental_rollouts.md). | ||||
| 
 | ||||
| ### Deployment safety | ||||
| ## CI/CD variables for environments and deployments | ||||
| 
 | ||||
| When you create an environment, you specify the name and URL. | ||||
| 
 | ||||
| If you want to use the name or URL in another job, you can use: | ||||
| 
 | ||||
| - `$CI_ENVIRONMENT_NAME`. The name defined in the `.gitlab-ci.yml` file. | ||||
| - `$CI_ENVIRONMENT_SLUG`. A "cleaned-up" version of the name, suitable for use in URLs, | ||||
|   DNS, etc. This variable is guaranteed to be unique. | ||||
| - `$CI_ENVIRONMENT_URL`. The environment's URL, which was specified in the | ||||
|   `.gitlab-ci.yml` file or automatically assigned. | ||||
| 
 | ||||
| If you change the name of an existing environment, the: | ||||
| 
 | ||||
| - `$CI_ENVIRONMENT_NAME` variable is updated with the new environment name. | ||||
| - `$CI_ENVIRONMENT_SLUG` variable remains unchanged to prevent unintended side | ||||
|   effects. | ||||
| 
 | ||||
| ## Set dynamic environment URLs after a job finishes | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17066) in GitLab 12.9. | ||||
| 
 | ||||
| In a job script, you can specify a static environment URL. | ||||
| However, there may be times when you want a dynamic URL. For example, | ||||
| if you deploy a Review App to an external hosting | ||||
| service that generates a random URL per deployment, like `https://94dd65b.amazonaws.com/qa-lambda-1234567`, | ||||
| you don't know the URL before the deployment script finishes. | ||||
| If you want to use the environment URL in GitLab, you would have to update it manually. | ||||
| 
 | ||||
| To address this problem, you can configure a deployment job to report back a set of | ||||
| variables, including the URL that was dynamically-generated by the external service. | ||||
| GitLab supports the [dotenv (`.env`)](https://github.com/bkeepers/dotenv) file format, | ||||
| and expands the `environment:url` value with variables defined in the `.env` file. | ||||
| 
 | ||||
| To use this feature, specify the | ||||
| [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) keyword in `.gitlab-ci.yml`. | ||||
| 
 | ||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> | ||||
| For an overview, see [Set dynamic URLs after a job finished](https://youtu.be/70jDXtOf4Ig). | ||||
| 
 | ||||
| ### Example of setting dynamic environment URLs | ||||
| 
 | ||||
| The following example shows a Review App that creates a new environment | ||||
| per merge request. The `review` job is triggered by every push, and | ||||
| creates or updates an environment named `review/your-branch-name`. | ||||
| The environment URL is set to `$DYNAMIC_ENVIRONMENT_URL`: | ||||
| 
 | ||||
| ```yaml | ||||
| review: | ||||
|   script: | ||||
|     - DYNAMIC_ENVIRONMENT_URL=$(deploy-script)                                 # In script, get the environment URL. | ||||
|     - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env    # Add the value to a dotenv file. | ||||
|   artifacts: | ||||
|     reports: | ||||
|       dotenv: deploy.env                                                       # Report back dotenv file to rails. | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_SLUG | ||||
|     url: $DYNAMIC_ENVIRONMENT_URL                                              # and set the variable produced in script to `environment:url` | ||||
|     on_stop: stop_review | ||||
| 
 | ||||
| stop_review: | ||||
|   script: | ||||
|     - ./teardown-environment | ||||
|   when: manual | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_SLUG | ||||
|     action: stop | ||||
| ``` | ||||
| 
 | ||||
| As soon as the `review` job finishes, GitLab updates the `review/your-branch-name` | ||||
| environment's URL. | ||||
| It parses the `deploy.env` report artifact, registers a list of variables as runtime-created, | ||||
| uses it for expanding `environment:url: $DYNAMIC_ENVIRONMENT_URL` and sets it to the environment URL. | ||||
| You can also specify a static part of the URL at `environment:url:`, such as | ||||
| `https://$DYNAMIC_ENVIRONMENT_URL`. If the value of `DYNAMIC_ENVIRONMENT_URL` is | ||||
| `example.com`, the final result is `https://example.com`. | ||||
| 
 | ||||
| The assigned URL for the `review/your-branch-name` environment is visible in the UI. | ||||
| 
 | ||||
| Note the following: | ||||
| 
 | ||||
| - `stop_review` doesn't generate a dotenv report artifact, so it doesn't recognize the | ||||
|   `DYNAMIC_ENVIRONMENT_URL` environment variable. Therefore you shouldn't set `environment:url:` in the | ||||
|   `stop_review` job. | ||||
| - If the environment URL isn't valid (for example, the URL is malformed), the system doesn't update | ||||
|   the environment URL. | ||||
| - If the script that runs in `stop_review` exists only in your repository and therefore can't use | ||||
|   `GIT_STRATEGY: none`, configure [pipelines for merge requests](../../ci/merge_request_pipelines/index.md) | ||||
|   for these jobs. This ensures that runners can fetch the repository even after a feature branch is | ||||
|   deleted. For more information, see [Ref Specs for Runners](../pipelines/index.md#ref-specs-for-runners). | ||||
| 
 | ||||
| ## Deployment safety | ||||
| 
 | ||||
| Deployment jobs can be more sensitive than other jobs in a pipeline, | ||||
| and might need to be treated with an extra care. There are multiple features | ||||
| in GitLab that helps maintain deployment security and stability. | ||||
| in GitLab that help maintain deployment security and stability. | ||||
| 
 | ||||
| - [Restrict write-access to a critical environment](deployment_safety.md#restrict-write-access-to-a-critical-environment) | ||||
| - [Limit the job-concurrency for deployment jobs](deployment_safety.md#ensure-only-one-deployment-job-runs-at-a-time) | ||||
| - [Skip outdated deployment jobs](deployment_safety.md#skip-outdated-deployment-jobs) | ||||
| - [Prevent deployments during deploy freeze windows](deployment_safety.md#prevent-deployments-during-deploy-freeze-windows) | ||||
| 
 | ||||
| ### Complete example | ||||
| 
 | ||||
| The configuration in this section provides a full development workflow where your app is: | ||||
| 
 | ||||
| - Tested. | ||||
| - Built. | ||||
| - Deployed as a Review App. | ||||
| - Deployed to a staging server after the merge request is merged. | ||||
| - Finally, able to be manually deployed to the production server. | ||||
| 
 | ||||
| The following combines the previous configuration examples, including: | ||||
| 
 | ||||
| - Defining [simple environments](#defining-environments) for testing, building, and deployment to staging. | ||||
| - Adding [manual actions](#configuring-manual-deployments) for deployment to production. | ||||
| - Creating [dynamic environments](#configuring-dynamic-environments) for deployments for reviewing. | ||||
| 
 | ||||
| ```yaml | ||||
| stages: | ||||
|   - test | ||||
|   - build | ||||
|   - deploy | ||||
| 
 | ||||
| test: | ||||
|   stage: test | ||||
|   script: echo "Running tests" | ||||
| 
 | ||||
| build: | ||||
|   stage: build | ||||
|   script: echo "Building the app" | ||||
| 
 | ||||
| deploy_review: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy a review app" | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_NAME | ||||
|     url: https://$CI_ENVIRONMENT_SLUG.example.com | ||||
|   only: | ||||
|     - branches | ||||
|   except: | ||||
|     - master | ||||
| 
 | ||||
| deploy_staging: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to staging server" | ||||
|   environment: | ||||
|     name: staging | ||||
|     url: https://staging.example.com | ||||
|   only: | ||||
|     - master | ||||
| 
 | ||||
| deploy_prod: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - echo "Deploy to production server" | ||||
|   environment: | ||||
|     name: production | ||||
|     url: https://example.com | ||||
|   when: manual | ||||
|   only: | ||||
|     - master | ||||
| ``` | ||||
| 
 | ||||
| A more realistic example would also include copying files to a location where a | ||||
| webserver (for example, NGINX) could then access and serve them. | ||||
| 
 | ||||
| The example below copies the `public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`: | ||||
| 
 | ||||
| ```yaml | ||||
| review_app: | ||||
|   stage: deploy | ||||
|   script: | ||||
|     - rsync -av --delete public /srv/nginx/$CI_COMMIT_REF_SLUG | ||||
|   environment: | ||||
|     name: review/$CI_COMMIT_REF_NAME | ||||
|     url: https://$CI_COMMIT_REF_SLUG.example.com | ||||
| ``` | ||||
| 
 | ||||
| This example requires that NGINX and GitLab Runner are set up on the server this job runs on. | ||||
| 
 | ||||
| See the [limitations](#limitations) section for some edge cases regarding the naming of your | ||||
| branches and Review Apps. | ||||
| 
 | ||||
| The complete example provides the following workflow to developers: | ||||
| 
 | ||||
| - Create a branch locally. | ||||
| - Make changes and commit them. | ||||
| - Push the branch to GitLab. | ||||
| - Create a merge request. | ||||
| 
 | ||||
| Behind the scenes, the runner: | ||||
| 
 | ||||
| - Picks up the changes and starts running the jobs. | ||||
| - Runs the jobs sequentially as defined in `stages`: | ||||
|   - First, run the tests. | ||||
|   - If the tests succeed, build the app. | ||||
|   - If the build succeeds, the app is deployed to an environment with a name specific to the | ||||
|     branch. | ||||
| 
 | ||||
| So now, every branch: | ||||
| 
 | ||||
| - Gets its own environment. | ||||
| - Is deployed to its own unique location, with the added benefit of: | ||||
|   - Having a [history of deployments](#view-the-deployment-history). | ||||
|   - Being able to [roll back changes](#retry-or-roll-back-a-deployment) if needed. | ||||
| 
 | ||||
| For more information, see [Using the environment URL](#using-the-environment-url). | ||||
| 
 | ||||
| ### Protected environments | ||||
| ## Protected environments | ||||
| 
 | ||||
| Environments can be "protected", restricting access to them. | ||||
| 
 | ||||
|  | @ -538,36 +308,16 @@ For more information, see [Protected environments](protected_environments.md). | |||
| Once environments are configured, GitLab provides many features for working with them, | ||||
| as documented below. | ||||
| 
 | ||||
| ### View environments and deployments | ||||
| ### Environment rollback | ||||
| 
 | ||||
| Prerequisites: | ||||
| When you roll back a deployment on a specific commit, | ||||
| a _new_ deployment is created. This deployment has its own unique job ID. | ||||
| It points to the commit you're rolling back to. | ||||
| 
 | ||||
| - You must have a minimum of [Reporter permission](../../user/permissions.md#project-members-permissions). | ||||
| For the rollback to succeed, the deployment process must be defined in | ||||
| the job's `script`. | ||||
| 
 | ||||
| To view a list of environments and deployment statuses: | ||||
| 
 | ||||
| - Go to the project's **Operations > Environments** page. | ||||
| 
 | ||||
| The **Environments** page shows the latest deployments. | ||||
| 
 | ||||
| - An environment can have multiple deployments. Some deployments may not be listed on the page. | ||||
| - Only deploys that happen after your `.gitlab-ci.yml` is properly configured | ||||
|   show up in the **Environment** and **Last deployment** lists. | ||||
| 
 | ||||
| ### View the deployment history | ||||
| 
 | ||||
| GitLab tracks your deployments, so you: | ||||
| 
 | ||||
| - Always know what is currently deployed on your servers. | ||||
| - Have the full history of your deployments for every environment. | ||||
| 
 | ||||
| - Go to the project's **Operations > Environments** page. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| This view is similar to the **Environments** page, but all deployments are shown. | ||||
| 
 | ||||
| ### Retry or roll back a deployment | ||||
| #### Retry or roll back a deployment | ||||
| 
 | ||||
| If there is a problem with a deployment, you can retry it or roll it back. | ||||
| 
 | ||||
|  | @ -575,32 +325,23 @@ To retry or rollback a deployment: | |||
| 
 | ||||
| 1. Go to the project's **Operations > Environments**. | ||||
| 1. Select the environment. | ||||
| 1. In the deployment history list for the environment: | ||||
|    - To retry a deployment, select **Retry**. | ||||
|    - to roll back to a deployment, next to a previously successful deployment, select **Rollback**. | ||||
| 1. To the right of the deployment name: | ||||
|    - To retry a deployment, select **Re-deploy to environment**. | ||||
|    - To roll back to a deployment, next to a previously successful deployment, select **Rollback environment**. | ||||
| 
 | ||||
| #### What to expect with a rollback | ||||
| 
 | ||||
| Pressing the **Rollback** button on a specific commit triggers a _new_ deployment with its own | ||||
| unique job ID. This new deployment points to the commit you're | ||||
| rolling back to. | ||||
| 
 | ||||
| Note that the defined deployment process in the job's `script` determines whether the rollback | ||||
| succeeds. | ||||
| 
 | ||||
| ### Using the environment URL | ||||
| ### Environment URL | ||||
| 
 | ||||
| The [environment URL](../yaml/README.md#environmenturl) is exposed in a few | ||||
| places within GitLab: | ||||
| 
 | ||||
| - In a merge request widget as a link: | ||||
| - In a merge request as a link: | ||||
|    | ||||
| - In the Environments view as a button: | ||||
|    | ||||
|    | ||||
| - In the Deployments view as a button: | ||||
|    | ||||
| 
 | ||||
| You can see this information in a merge request itself if: | ||||
| You can see this information in a merge request if: | ||||
| 
 | ||||
| - The merge request is eventually merged to the default branch (usually `master`). | ||||
| - That branch also deploys to an environment (for example, `staging` or `production`). | ||||
|  | @ -616,19 +357,16 @@ from source files to public pages in the environment set for Review Apps. | |||
| 
 | ||||
| ### Stopping an environment | ||||
| 
 | ||||
| Stopping an environment: | ||||
| When you stop an environment: | ||||
| 
 | ||||
| - Moves it from the list of **Available** environments to the list of **Stopped** | ||||
| - It moves from the list of **Available** environments to the list of **Stopped** | ||||
|   environments on the [**Environments** page](#view-environments-and-deployments). | ||||
| - Executes an [`on_stop` action](../yaml/README.md#environmenton_stop), if defined. | ||||
| - An [`on_stop` action](../yaml/README.md#environmenton_stop), if defined, is executed. | ||||
| 
 | ||||
| This is often used when multiple developers are working on a project at the same time, | ||||
| each of them pushing to their own branches, causing many dynamic environments to be created. | ||||
| 
 | ||||
| Starting with GitLab 8.14, dynamic environments stop automatically when their associated branch is | ||||
| Dynamic environments stop automatically when their associated branch is | ||||
| deleted. | ||||
| 
 | ||||
| #### Automatically stopping an environment | ||||
| #### Stop an environment when a branch is deleted | ||||
| 
 | ||||
| Environments can be stopped automatically using special configuration. | ||||
| 
 | ||||
|  | @ -678,7 +416,7 @@ possible to trigger `action: stop` to stop the environment automatically. | |||
| 
 | ||||
| You can read more in the [`.gitlab-ci.yml` reference](../yaml/README.md#environmenton_stop). | ||||
| 
 | ||||
| #### Environments auto-stop | ||||
| #### Stop an environment after a certain time period | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20956) in GitLab 12.8. | ||||
| 
 | ||||
|  | @ -763,7 +501,7 @@ environment name to see its details and **Delete** it from there. | |||
| You can also delete environments by viewing the details for a | ||||
| stopped environment: | ||||
| 
 | ||||
|   1. Navigate to **Operations > Environments**. | ||||
|   1. Go to the project's **Operations > Environments** page. | ||||
|   1. Click on the name of an environment within the **Stopped** environments list. | ||||
|   1. Click on the **Delete** button that appears at the top for all stopped environments. | ||||
|   1. Finally, confirm your chosen environment in the modal that appears to delete it. | ||||
|  | @ -776,7 +514,7 @@ Environments can also be deleted by using the [Environments API](../../api/envir | |||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208655) in GitLab 13.2. | ||||
| 
 | ||||
| By default, GitLab creates a [deployment](#view-the-deployment-history) every time a | ||||
| By default, GitLab creates a deployment every time a | ||||
| build with the specified environment runs. Newer deployments can also | ||||
| [cancel older ones](deployment_safety.md#skip-outdated-deployment-jobs). | ||||
| 
 | ||||
|  | @ -801,15 +539,15 @@ build: | |||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7015) in GitLab 8.14. | ||||
| 
 | ||||
| As documented in [Configuring dynamic environments](#configuring-dynamic-environments), you can | ||||
| As documented in [Create a dynamic environment](#create-a-dynamic-environment), you can | ||||
| prepend environment name with a word, followed by a `/`, and finally the branch | ||||
| name, which is automatically defined by the `CI_COMMIT_REF_NAME` predefined CI/CD variable. | ||||
| 
 | ||||
| In short, environments that are named like `type/foo` are all presented under the same | ||||
| group, named `type`. | ||||
| 
 | ||||
| In our [minimal example](#example-configuration), we named the environments `review/$CI_COMMIT_REF_NAME` | ||||
| where `$CI_COMMIT_REF_NAME` is the branch name. Here is a snippet of the example: | ||||
| If you name the environments `review/$CI_COMMIT_REF_NAME` | ||||
| where `$CI_COMMIT_REF_NAME` is the branch name: | ||||
| 
 | ||||
| ```yaml | ||||
| deploy_review: | ||||
|  | @ -820,7 +558,7 @@ deploy_review: | |||
|     name: review/$CI_COMMIT_REF_NAME | ||||
| ``` | ||||
| 
 | ||||
| In this case, if you visit the **Environments** page and the branches | ||||
| If you visit the **Environments** page and the branches | ||||
| exist, you should see something like: | ||||
| 
 | ||||
|  | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 39 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 43 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 28 KiB | 
|  | @ -103,7 +103,7 @@ Feature.enable(:ci_config_visualization_tab) | |||
| ## View expanded configuration | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/246801) in GitLab 13.9. | ||||
| > - It is [deployed behind a feature flag](../../user/feature_flags.md), disabled by default. | ||||
| > - It is [deployed behind a feature flag](../../user/feature_flags.md), enabled by default. | ||||
| > - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-expanded-configuration). **(FREE SELF)** | ||||
| 
 | ||||
| To view the fully expanded CI/CD configuration as one combined file, go to the | ||||
|  | @ -118,20 +118,20 @@ where: | |||
| ### Enable or disable expanded configuration **(FREE SELF)** | ||||
| 
 | ||||
| Expanded CI/CD configuration is under development and not ready for production use. | ||||
| It is deployed behind a feature flag that is **disabled by default**. | ||||
| It is deployed behind a feature flag that is **enabled by default**. | ||||
| [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) | ||||
| can opt to enable it. | ||||
| 
 | ||||
| To enable it: | ||||
| 
 | ||||
| ```ruby | ||||
| Feature.enable(:ci_config_visualization_tab) | ||||
| ``` | ||||
| 
 | ||||
| To disable it: | ||||
| 
 | ||||
| ```ruby | ||||
| Feature.disable(:ci_config_visualization_tab) | ||||
| Feature.disable(:ci_config_merged_tab) | ||||
| ``` | ||||
| 
 | ||||
| To enable it: | ||||
| 
 | ||||
| ```ruby | ||||
| Feature.enable(:ci_config_merged_tab) | ||||
| ``` | ||||
| 
 | ||||
| ## Commit changes to CI configuration | ||||
|  |  | |||
|  | @ -96,7 +96,7 @@ This table lists the refspecs injected for each pipeline type: | |||
| The refs `refs/heads/<name>` and `refs/tags/<name>` exist in your | ||||
| project repository. GitLab generates the special ref `refs/pipelines/<id>` during a | ||||
| running pipeline job. This ref can be created even after the associated branch or tag has been | ||||
| deleted. It's therefore useful in some features such as [automatically stopping an environment](../environments/index.md#automatically-stopping-an-environment), | ||||
| deleted. It's therefore useful in some features such as [automatically stopping an environment](../environments/index.md#stopping-an-environment), | ||||
| and [merge trains](../merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md) | ||||
| that might run pipelines after branch deletion. | ||||
| 
 | ||||
|  | @ -208,7 +208,7 @@ You can do this straight from the pipeline graph. Just click the play button | |||
| to execute that particular job. | ||||
| 
 | ||||
| For example, your pipeline might start automatically, but it requires manual action to | ||||
| [deploy to production](../environments/index.md#configuring-manual-deployments). In the example below, the `production` | ||||
| [deploy to production](../environments/index.md#configure-manual-deployments). In the example below, the `production` | ||||
| stage has a job with a manual action. | ||||
| 
 | ||||
|  | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ After adding Review Apps to your workflow, you follow the branched Git flow. Tha | |||
| 
 | ||||
| ## Configuring Review Apps | ||||
| 
 | ||||
| Review Apps are built on [dynamic environments](../environments/index.md#configuring-dynamic-environments), which allow you to dynamically create a new environment for each branch. | ||||
| Review Apps are built on [dynamic environments](../environments/index.md#create-a-dynamic-environment), which allow you to dynamically create a new environment for each branch. | ||||
| 
 | ||||
| The process of configuring Review Apps is as follows: | ||||
| 
 | ||||
|  | @ -89,7 +89,7 @@ you can copy and paste into `.gitlab-ci.yml` as a starting point. To do so: | |||
| 
 | ||||
| ## Review Apps auto-stop | ||||
| 
 | ||||
| See how to [configure Review Apps environments to expire and auto-stop](../environments/index.md#environments-auto-stop) | ||||
| See how to [configure Review Apps environments to expire and auto-stop](../environments/index.md#stop-an-environment-after-a-certain-time-period) | ||||
| after a given period of time. | ||||
| 
 | ||||
| ## Review Apps examples | ||||
|  |  | |||
|  | @ -2322,7 +2322,7 @@ started by a user. You might want to use manual jobs for things like deploying t | |||
| 
 | ||||
| To make a job manual, add `when: manual` to its configuration. | ||||
| 
 | ||||
| Manual jobs can be started from the pipeline, job, [environment](../environments/index.md#configuring-manual-deployments), | ||||
| Manual jobs can be started from the pipeline, job, [environment](../environments/index.md#configure-manual-deployments), | ||||
| and deployment views. | ||||
| 
 | ||||
| Manual jobs can be either optional or blocking: | ||||
|  | @ -2562,7 +2562,7 @@ it is set to `manual`, so it needs a [manual action](#whenmanual) from | |||
| the GitLab UI to run. | ||||
| 
 | ||||
| Also in the example, `GIT_STRATEGY` is set to `none`. If the | ||||
| `stop_review_app` job is [automatically triggered](../environments/index.md#automatically-stopping-an-environment), | ||||
| `stop_review_app` job is [automatically triggered](../environments/index.md#stopping-an-environment), | ||||
| the runner won’t try to check out the code after the branch is deleted. | ||||
| 
 | ||||
| The example also overwrites global variables. If your `stop` `environment` job depends | ||||
|  | @ -2608,7 +2608,7 @@ When the environment for `review_app` is created, the environment's lifetime is | |||
| Every time the review app is deployed, that lifetime is also reset to `1 day`. | ||||
| 
 | ||||
| For more information, see | ||||
| [the environments auto-stop documentation](../environments/index.md#environments-auto-stop) | ||||
| [the environments auto-stop documentation](../environments/index.md#stop-an-environment-after-a-certain-time-period) | ||||
| 
 | ||||
| #### `environment:kubernetes` | ||||
| 
 | ||||
|  | @ -2634,7 +2634,7 @@ environment, using the `production` | |||
| [Kubernetes namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/). | ||||
| 
 | ||||
| For more information, see | ||||
| [Available settings for `kubernetes`](../environments/index.md#configuring-kubernetes-deployments). | ||||
| [Available settings for `kubernetes`](../environments/index.md#configure-kubernetes-deployments). | ||||
| 
 | ||||
| NOTE: | ||||
| Kubernetes configuration is not supported for Kubernetes clusters | ||||
|  |  | |||
|  | @ -109,7 +109,7 @@ subgraph "CNG-mirror pipeline" | |||
| ### Auto-stopping of Review Apps | ||||
| 
 | ||||
| Review Apps are automatically stopped 2 days after the last deployment thanks to | ||||
| the [Environment auto-stop](../../ci/environments/index.md#environments-auto-stop) feature. | ||||
| the [Environment auto-stop](../../ci/environments/index.md#stop-an-environment-after-a-certain-time-period) feature. | ||||
| 
 | ||||
| If you need your Review App to stay up for a longer time, you can | ||||
| [pin its environment](../../ci/environments/index.md#auto-stop-example) or retry the | ||||
|  |  | |||
|  | @ -204,7 +204,7 @@ into your project and edit it as needed. | |||
| 
 | ||||
| For clusters not managed by GitLab, you can customize the namespace in | ||||
| `.gitlab-ci.yml` by specifying | ||||
| [`environment:kubernetes:namespace`](../../ci/environments/index.md#configuring-kubernetes-deployments). | ||||
| [`environment:kubernetes:namespace`](../../ci/environments/index.md#configure-kubernetes-deployments). | ||||
| For example, the following configuration overrides the namespace used for | ||||
| `production` deployments: | ||||
| 
 | ||||
|  |  | |||
|  | @ -312,7 +312,7 @@ following command in your deployment job script, for Kubernetes to access the re | |||
| The Kubernetes cluster integration exposes these | ||||
| [deployment variables](../../../ci/variables/README.md#deployment-variables) in the | ||||
| GitLab CI/CD build environment to deployment jobs. Deployment jobs have | ||||
| [defined a target environment](../../../ci/environments/index.md#defining-environments). | ||||
| [defined a target environment](../../../ci/environments/index.md). | ||||
| 
 | ||||
| | Deployment Variable        | Description | | ||||
| |----------------------------|-------------| | ||||
|  | @ -345,7 +345,7 @@ You can customize the deployment namespace in a few ways: | |||
| - For **non-managed** clusters, the auto-generated namespace is set in the `KUBECONFIG`, | ||||
|   but the user is responsible for ensuring its existence. You can fully customize | ||||
|   this value using | ||||
|   [`environment:kubernetes:namespace`](../../../ci/environments/index.md#configuring-kubernetes-deployments) | ||||
|   [`environment:kubernetes:namespace`](../../../ci/environments/index.md#configure-kubernetes-deployments) | ||||
|   in `.gitlab-ci.yml`. | ||||
| 
 | ||||
| When you customize the namespace, existing environments remain linked to their current | ||||
|  | @ -432,7 +432,7 @@ Reasons for failure include: | |||
| - The token you gave GitLab does not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) | ||||
|   privileges required by GitLab. | ||||
| - Missing `KUBECONFIG` or `KUBE_TOKEN` deployment variables. To be passed to your job, they must have a matching | ||||
|   [`environment:name`](../../../ci/environments/index.md#defining-environments). If your job has no | ||||
|   [`environment:name`](../../../ci/environments/index.md). If your job has no | ||||
|   `environment:name` set, the Kubernetes credentials are not passed to it. | ||||
| 
 | ||||
| NOTE: | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ specific environment, there are a lot of use cases. To name a few: | |||
| 
 | ||||
| To display the Deploy Boards for a specific [environment](../../ci/environments/index.md) you should: | ||||
| 
 | ||||
| 1. Have [defined an environment](../../ci/environments/index.md#defining-environments) with a deploy stage. | ||||
| 1. Have [defined an environment](../../ci/environments/index.md) with a deploy stage. | ||||
| 
 | ||||
| 1. Have a Kubernetes cluster up and running. | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,7 @@ | |||
| module API | ||||
|   module Entities | ||||
|     class UserSafe < Grape::Entity | ||||
|       expose :id, :username | ||||
|       expose :name, unless: ->(user) { user.project_bot? && !options[:current_user].admin?} | ||||
|       expose :id, :name, :username | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -9,8 +9,6 @@ module Gitlab | |||
|       class ProjectPipelineStatus | ||||
|         include Gitlab::Utils::StrongMemoize | ||||
| 
 | ||||
|         ALL_PIPELINES_STATUS_PATTERN = 'projects/*/pipeline_status' | ||||
| 
 | ||||
|         attr_accessor :sha, :status, :ref, :project, :loaded | ||||
| 
 | ||||
|         def self.load_for_project(project) | ||||
|  |  | |||
|  | @ -7,11 +7,14 @@ module Gitlab | |||
|         class Item | ||||
|           include Gitlab::Utils::StrongMemoize | ||||
| 
 | ||||
|           def initialize(key:, value:, public: true, file: false, masked: false) | ||||
|           attr_reader :raw | ||||
| 
 | ||||
|           def initialize(key:, value:, public: true, file: false, masked: false, raw: false) | ||||
|             raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless | ||||
|               value.is_a?(String) || value.nil? | ||||
| 
 | ||||
|             @variable = { key: key, value: value, public: public, file: file, masked: masked } | ||||
|             @raw = raw | ||||
|           end | ||||
| 
 | ||||
|           def value | ||||
|  | @ -28,6 +31,8 @@ module Gitlab | |||
| 
 | ||||
|           def depends_on | ||||
|             strong_memoize(:depends_on) do | ||||
|               next if raw | ||||
| 
 | ||||
|               next unless ExpandVariables.possible_var_reference?(value) | ||||
| 
 | ||||
|               value.scan(ExpandVariables::VARIABLES_REGEXP).map(&:first) | ||||
|  |  | |||
|  | @ -1,49 +0,0 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module Gitlab | ||||
|   module Cleanup | ||||
|     module Redis | ||||
|       class BatchDeleteByPattern | ||||
|         REDIS_CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000 | ||||
|         REDIS_SCAN_START_STOP = '0'.freeze # Magic value, see http://redis.io/commands/scan | ||||
| 
 | ||||
|         attr_reader :patterns | ||||
| 
 | ||||
|         def initialize(patterns) | ||||
|           raise ArgumentError.new('Argument should be an Array of patterns') unless patterns.is_a?(Array) | ||||
| 
 | ||||
|           @patterns = patterns | ||||
|         end | ||||
| 
 | ||||
|         def execute | ||||
|           return if patterns.blank? | ||||
| 
 | ||||
|           batch_delete_cache_keys | ||||
|         end | ||||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         def batch_delete_cache_keys | ||||
|           Gitlab::Redis::Cache.with do |redis| | ||||
|             patterns.each do |match| | ||||
|               cursor = REDIS_SCAN_START_STOP | ||||
|               loop do | ||||
|                 cursor, keys = redis.scan( | ||||
|                   cursor, | ||||
|                   match: match, | ||||
|                   count: REDIS_CLEAR_BATCH_SIZE | ||||
|                 ) | ||||
| 
 | ||||
|                 Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do | ||||
|                   redis.del(*keys) if keys.any? | ||||
|                 end | ||||
| 
 | ||||
|                 break if cursor == REDIS_SCAN_START_STOP | ||||
|               end | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -1,88 +0,0 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module Gitlab | ||||
|   module Cleanup | ||||
|     module Redis | ||||
|       class DescriptionTemplatesCacheKeysPatternBuilder | ||||
|         # project_ids - a list of project_ids for which to compute description templates cache keys or `:all` to compute | ||||
|         # a pattern that cover all description templates cache keys. | ||||
|         # | ||||
|         # Example | ||||
|         # * ::Gitlab::Cleanup::Redis::BatchDeleteDescriptionTemplates.new(:all).execute - to get 2 | ||||
|         # patterns for all issue and merge request description templates cache keys. | ||||
|         # | ||||
|         # * ::Gitlab::Cleanup::Redis::BatchDeleteDescriptionTemplates.new([1,2,3,4]).execute - to get an array of | ||||
|         # patterns for each project's issue and merge request description templates cache keys. | ||||
|         def initialize(project_ids) | ||||
|           raise ArgumentError.new('project_ids can either be an array of project IDs or :all') if project_ids != :all && !project_ids.is_a?(Array) | ||||
| 
 | ||||
|           @project_ids = parse_project_ids(project_ids) | ||||
|         end | ||||
| 
 | ||||
|         def execute | ||||
|           case project_ids | ||||
|           when :all | ||||
|             all_instance_patterns | ||||
|           else | ||||
|             project_patterns | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         attr_reader :project_ids | ||||
| 
 | ||||
|         def parse_project_ids(project_ids) | ||||
|           return project_ids if project_ids == :all | ||||
| 
 | ||||
|           project_ids.map { |id| Integer(id) } | ||||
|         rescue ArgumentError | ||||
|           raise ArgumentError.new('Invalid Project ID. Please ensure all passed in project ids values are valid integer project ids.') | ||||
|         end | ||||
| 
 | ||||
|         def project_patterns | ||||
|           cache_key_patterns = [] | ||||
|           Project.id_in(project_ids).each_batch do |batch| | ||||
|             cache_key_patterns << batch.map do |pr| | ||||
|               next unless pr.repository.exists? | ||||
| 
 | ||||
|               cache = Gitlab::RepositoryCache.new(pr.repository) | ||||
| 
 | ||||
|               [repo_issue_templates_cache_key(cache), repo_merge_request_templates_cache_key(cache)] | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           cache_key_patterns.flatten.compact | ||||
|         end | ||||
| 
 | ||||
|         def all_instance_patterns | ||||
|           [all_issue_templates_cache_key, all_merge_request_templates_cache_key] | ||||
|         end | ||||
| 
 | ||||
|         def issue_templates_cache_key | ||||
|           Repository::METHOD_CACHES_FOR_FILE_TYPES[:issue_template] | ||||
|         end | ||||
| 
 | ||||
|         def merge_request_templates_cache_key | ||||
|           Repository::METHOD_CACHES_FOR_FILE_TYPES[:merge_request_template] | ||||
|         end | ||||
| 
 | ||||
|         def all_issue_templates_cache_key | ||||
|           "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:#{issue_templates_cache_key}:*" | ||||
|         end | ||||
| 
 | ||||
|         def all_merge_request_templates_cache_key | ||||
|           "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:#{merge_request_templates_cache_key}:*" | ||||
|         end | ||||
| 
 | ||||
|         def repo_issue_templates_cache_key(cache) | ||||
|           "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:#{cache.cache_key(issue_templates_cache_key)}" | ||||
|         end | ||||
| 
 | ||||
|         def repo_merge_request_templates_cache_key(cache) | ||||
|           "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:#{cache.cache_key(merge_request_templates_cache_key)}" | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -2,22 +2,32 @@ | |||
| 
 | ||||
| namespace :cache do | ||||
|   namespace :clear do | ||||
|     REDIS_CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000 | ||||
|     REDIS_SCAN_START_STOP = '0'.freeze # Magic value, see http://redis.io/commands/scan | ||||
| 
 | ||||
|     desc "GitLab | Cache | Clear redis cache" | ||||
|     task redis: :environment do | ||||
|       cache_key_patterns = %W[ | ||||
|         #{Gitlab::Redis::Cache::CACHE_NAMESPACE}* | ||||
|         #{Gitlab::Cache::Ci::ProjectPipelineStatus::ALL_PIPELINES_STATUS_PATTERN} | ||||
|       ] | ||||
|       Gitlab::Redis::Cache.with do |redis| | ||||
|         cache_key_pattern = %W[#{Gitlab::Redis::Cache::CACHE_NAMESPACE}* | ||||
|                                projects/*/pipeline_status] | ||||
| 
 | ||||
|       ::Gitlab::Cleanup::Redis::BatchDeleteByPattern.new(cache_key_patterns).execute | ||||
|     end | ||||
|         cache_key_pattern.each do |match| | ||||
|           cursor = REDIS_SCAN_START_STOP | ||||
|           loop do | ||||
|             cursor, keys = redis.scan( | ||||
|               cursor, | ||||
|               match: match, | ||||
|               count: REDIS_CLEAR_BATCH_SIZE | ||||
|             ) | ||||
| 
 | ||||
|     desc "GitLab | Cache | Clear description templates redis cache" | ||||
|     task description_templates: :environment do | ||||
|       project_ids = Array(ENV['project_ids']&.split(',')).map!(&:squish) | ||||
|             Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do | ||||
|               redis.del(*keys) if keys.any? | ||||
|             end | ||||
| 
 | ||||
|       cache_key_patterns = ::Gitlab::Cleanup::Redis::DescriptionTemplatesCacheKeysPatternBuilder.new(project_ids).execute | ||||
|       ::Gitlab::Cleanup::Redis::BatchDeleteByPattern.new(cache_key_patterns).execute | ||||
|             break if cursor == REDIS_SCAN_START_STOP | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     task all: [:redis] | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ module DeprecationToolkitEnv | |||
|       carrierwave-1.3.1/lib/carrierwave/sanitized_file.rb | ||||
|       activerecord-6.0.3.4/lib/active_record/relation.rb | ||||
|       selenium-webdriver-3.142.7/lib/selenium/webdriver/firefox/driver.rb | ||||
|       asciidoctor-2.0.12/lib/asciidoctor/extensions.rb | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -475,7 +475,7 @@ FactoryBot.define do | |||
|     trait :license_scanning do | ||||
|       options do | ||||
|         { | ||||
|           artifacts: { reports: { license_management: 'gl-license-scanning-report.json' } } | ||||
|           artifacts: { reports: { license_scanning: 'gl-license-scanning-report.json' } } | ||||
|         } | ||||
|       end | ||||
|     end | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -127,7 +127,7 @@ RSpec.describe 'Project members list' do | |||
|       it 'does not show form used to change roles and "Expiration date" or the remove user button' do | ||||
|         visit_members_page | ||||
| 
 | ||||
|         page.within find_username_row(project_bot) do | ||||
|         page.within find_member_row(project_bot) do | ||||
|           expect(page).not_to have_button('Maintainer') | ||||
|           expect(page).to have_field('Expiration date', disabled: true) | ||||
|           expect(page).not_to have_button('Remove member') | ||||
|  |  | |||
|  | @ -15,10 +15,9 @@ RSpec.describe Security::LicenseComplianceJobsFinder do | |||
|     let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) } | ||||
|     let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) } | ||||
|     let!(:license_scanning_build) { create(:ci_build, :license_scanning, pipeline: pipeline) } | ||||
|     let!(:license_management_build) { create(:ci_build, :license_management, pipeline: pipeline) } | ||||
| 
 | ||||
|     it 'returns only the license_scanning jobs' do | ||||
|       is_expected.to contain_exactly(license_scanning_build, license_management_build) | ||||
|     it 'returns only the license_scanning job' do | ||||
|       is_expected.to contain_exactly(license_scanning_build) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -6,6 +6,18 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|   include GraphqlHelpers | ||||
| 
 | ||||
|   let(:current_user) { create(:user) } | ||||
|   let(:include_subgroups) { true } | ||||
|   let(:sort) { nil } | ||||
|   let(:search) { nil } | ||||
|   let(:ids) { nil } | ||||
|   let(:args) do | ||||
|     { | ||||
|       include_subgroups: include_subgroups, | ||||
|       sort: sort, | ||||
|       search: search, | ||||
|       ids: ids | ||||
|     } | ||||
|   end | ||||
| 
 | ||||
|   context "with a group" do | ||||
|     let(:group) { create(:group) } | ||||
|  | @ -27,7 +39,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|       end | ||||
| 
 | ||||
|       it 'finds all projects including the subgroups' do | ||||
|         expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2, nested_project) | ||||
|         expect(resolve_projects(args)).to contain_exactly(project1, project2, nested_project) | ||||
|       end | ||||
| 
 | ||||
|       context 'with an user namespace' do | ||||
|  | @ -38,7 +50,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|         end | ||||
| 
 | ||||
|         it 'finds all projects including the subgroups' do | ||||
|           expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2) | ||||
|           expect(resolve_projects(args)).to contain_exactly(project1, project2) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | @ -48,6 +60,9 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|       let(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: namespace) } | ||||
|       let(:project_3) { create(:project, name: 'Test', path: 'test', namespace: namespace) } | ||||
| 
 | ||||
|       let(:sort) { :similarity } | ||||
|       let(:search) { 'test' } | ||||
| 
 | ||||
|       before do | ||||
|         project_1.add_developer(current_user) | ||||
|         project_2.add_developer(current_user) | ||||
|  | @ -55,7 +70,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|       end | ||||
| 
 | ||||
|       it 'returns projects ordered by similarity to the search input' do | ||||
|         projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test') | ||||
|         projects = resolve_projects(args) | ||||
| 
 | ||||
|         project_names = projects.map { |proj| proj['name'] } | ||||
|         expect(project_names.first).to eq('Test') | ||||
|  | @ -63,15 +78,17 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|       end | ||||
| 
 | ||||
|       it 'filters out result that do not match the search input' do | ||||
|         projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test') | ||||
|         projects = resolve_projects(args) | ||||
| 
 | ||||
|         project_names = projects.map { |proj| proj['name'] } | ||||
|         expect(project_names).not_to include('Project') | ||||
|       end | ||||
| 
 | ||||
|       context 'when `search` parameter is not given' do | ||||
|         let(:search) { nil } | ||||
| 
 | ||||
|         it 'returns projects not ordered by similarity' do | ||||
|           projects = resolve_projects(include_subgroups: true, sort: :similarity, search: nil) | ||||
|           projects = resolve_projects(args) | ||||
| 
 | ||||
|           project_names = projects.map { |proj| proj['name'] } | ||||
|           expect(project_names.first).not_to eq('Test') | ||||
|  | @ -79,14 +96,40 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|       end | ||||
| 
 | ||||
|       context 'when only search term is given' do | ||||
|         let(:sort) { nil } | ||||
|         let(:search) { 'test' } | ||||
| 
 | ||||
|         it 'filters out result that do not match the search input, but does not sort them' do | ||||
|           projects = resolve_projects(include_subgroups: true, sort: :nil, search: 'test') | ||||
|           projects = resolve_projects(args) | ||||
| 
 | ||||
|           project_names = projects.map { |proj| proj['name'] } | ||||
|           expect(project_names).to contain_exactly('Test', 'Test Project') | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'ids filtering' do | ||||
|       subject(:projects) { resolve_projects(args) } | ||||
| 
 | ||||
|       let(:include_subgroups) { false } | ||||
|       let(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) } | ||||
| 
 | ||||
|       context 'when ids is provided' do | ||||
|         let(:ids) { [project_3.to_global_id.to_s] } | ||||
| 
 | ||||
|         it 'returns matching project' do | ||||
|           expect(projects).to contain_exactly(project_3) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when ids is nil' do | ||||
|         let(:ids) { nil } | ||||
| 
 | ||||
|         it 'returns all projects' do | ||||
|           expect(projects).to contain_exactly(project1, project2, project_3) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context "when passing a non existent, batch loaded namespace" do | ||||
|  | @ -108,7 +151,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do | |||
|     expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 | ||||
|   end | ||||
| 
 | ||||
|   def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil }, context = { current_user: current_user }) | ||||
|   def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user }) | ||||
|     resolve(described_class, obj: namespace, args: args, ctx: context) | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -35,22 +35,4 @@ RSpec.describe API::Entities::User do | |||
|       expect(subject[:bot]).to eq(true) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'with project bot user' do | ||||
|     let(:user) { create(:user, :project_bot) } | ||||
| 
 | ||||
|     context 'when the requester is not an admin' do | ||||
|       it 'does not expose project bot user name' do | ||||
|         expect(subject).not_to include(:name) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when the requester is an admin' do | ||||
|       let(:current_user) { create(:user, :admin) } | ||||
| 
 | ||||
|       it 'exposes project bot user name' do | ||||
|         expect(subject).to include(:name) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -92,6 +92,10 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do | |||
|             variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3' }, | ||||
|             expected_depends_on: %w(VAR2 VAR3) | ||||
|           }, | ||||
|           "complex expansion in raw variable": { | ||||
|             variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3', raw: true }, | ||||
|             expected_depends_on: nil | ||||
|           }, | ||||
|           "complex expansions for Windows": { | ||||
|             variable: { key: 'variable3', value: 'key%variable%%variable2%' }, | ||||
|             expected_depends_on: %w(variable variable2) | ||||
|  | @ -156,6 +160,26 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#raw' do | ||||
|     it 'returns false when :raw is not specified' do | ||||
|       item = described_class.new(**variable) | ||||
| 
 | ||||
|       expect(item.raw).to eq false | ||||
|     end | ||||
| 
 | ||||
|     context 'when :raw is specified as true' do | ||||
|       let(:variable) do | ||||
|         { key: variable_key, value: variable_value, public: true, masked: false, raw: true } | ||||
|       end | ||||
| 
 | ||||
|       it 'returns true' do | ||||
|         item = described_class.new(**variable) | ||||
| 
 | ||||
|         expect(item.raw).to eq true | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#to_runner_variable' do | ||||
|     context 'when variable is not a file-related' do | ||||
|       it 'returns a runner-compatible hash representation' do | ||||
|  | @ -185,5 +209,19 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do | |||
|         expect(runner_variable.depends_on).to eq(%w(CI_VAR_2 CI_VAR_3)) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when assigned the raw attribute' do | ||||
|       it 'retains a true raw attribute' do | ||||
|         runner_variable = described_class.new(key: 'CI_VAR', value: '123', raw: true) | ||||
| 
 | ||||
|         expect(runner_variable).to eq(key: 'CI_VAR', value: '123', public: true, masked: false, raw: true) | ||||
|       end | ||||
| 
 | ||||
|       it 'does not retain a false raw attribute' do | ||||
|         runner_variable = described_class.new(key: 'CI_VAR', value: '123', raw: false) | ||||
| 
 | ||||
|         expect(runner_variable).to eq(key: 'CI_VAR', value: '123', public: true, masked: false) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -76,6 +76,13 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do | |||
|                 { key: 'variable2', value: '$variable3' }, | ||||
|                 { key: 'variable3', value: 'key$variable$variable2' } | ||||
|               ] | ||||
|             }, | ||||
|             "array with raw variable": { | ||||
|               variables: [ | ||||
|                 { key: 'variable', value: '$variable2' }, | ||||
|                 { key: 'variable2', value: '$variable3' }, | ||||
|                 { key: 'variable3', value: 'key$variable$variable2', raw: true } | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|         end | ||||
|  | @ -128,6 +135,14 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do | |||
|                 { key: 'variable3', value: 'key$variable$variable2' } | ||||
|               ], | ||||
|               validation_result: 'circular variable reference detected: ["variable", "variable2", "variable3"]' | ||||
|             }, | ||||
|             "array with raw variable": { | ||||
|               variables: [ | ||||
|                 { key: 'variable', value: '$variable2' }, | ||||
|                 { key: 'variable2', value: '$variable3' }, | ||||
|                 { key: 'variable3', value: 'key$variable$variable2', raw: true } | ||||
|               ], | ||||
|               validation_result: nil | ||||
|             } | ||||
|           } | ||||
|         end | ||||
|  | @ -271,6 +286,22 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do | |||
|                 { key: 'variable', value: '$variable2' } | ||||
|               ], | ||||
|               result: %w[variable2 variable3 variable] | ||||
|             }, | ||||
|             "raw variable does not get resolved": { | ||||
|               variables: [ | ||||
|                 { key: 'variable', value: '$variable2' }, | ||||
|                 { key: 'variable2', value: '$variable3' }, | ||||
|                 { key: 'variable3', value: 'key$variable$variable2', raw: true } | ||||
|               ], | ||||
|               result: %w[variable3 variable2 variable] | ||||
|             }, | ||||
|             "variable containing escaped variable reference": { | ||||
|               variables: [ | ||||
|                 { key: 'variable_c', value: '$variable_b' }, | ||||
|                 { key: 'variable_b', value: '$$variable_a' }, | ||||
|                 { key: 'variable_a', value: 'value' } | ||||
|               ], | ||||
|               result: %w[variable_a variable_b variable_c] | ||||
|             } | ||||
|           } | ||||
|         end | ||||
|  |  | |||
|  | @ -1,91 +0,0 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Gitlab::Cleanup::Redis::BatchDeleteByPattern, :clean_gitlab_redis_cache do | ||||
|   subject { described_class.new(patterns) } | ||||
| 
 | ||||
|   describe 'execute' do | ||||
|     context 'when no patterns are passed' do | ||||
|       before do | ||||
|         expect(Gitlab::Redis::Cache).not_to receive(:with) | ||||
|       end | ||||
| 
 | ||||
|       context 'with nil patterns' do | ||||
|         let(:patterns) { nil } | ||||
| 
 | ||||
|         specify { expect { subject }.to raise_error(ArgumentError, 'Argument should be an Array of patterns') } | ||||
|       end | ||||
| 
 | ||||
|       context 'with empty array patterns' do | ||||
|         let(:patterns) { [] } | ||||
| 
 | ||||
|         specify { subject.execute } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with patterns' do | ||||
|       context 'when key is not found' do | ||||
|         let(:patterns) { ['key'] } | ||||
| 
 | ||||
|         before do | ||||
|           expect_any_instance_of(Redis).not_to receive(:del) # rubocop:disable RSpec/AnyInstanceOf | ||||
|         end | ||||
| 
 | ||||
|         specify { subject.execute } | ||||
|       end | ||||
| 
 | ||||
|       context 'with cache data' do | ||||
|         let(:cache_keys) { %w[key-test1 key-test2 key-test3 key-test4] } | ||||
| 
 | ||||
|         before do | ||||
|           stub_const("#{described_class}::REDIS_CLEAR_BATCH_SIZE", 2) | ||||
| 
 | ||||
|           write_to_cache | ||||
|         end | ||||
| 
 | ||||
|         context 'with one key' do | ||||
|           let(:patterns) { ['key-test1'] } | ||||
| 
 | ||||
|           it 'deletes the key' do | ||||
|             expect_any_instance_of(Redis).to receive(:del).with(patterns.first).once # rubocop:disable RSpec/AnyInstanceOf | ||||
| 
 | ||||
|             subject.execute | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'with many keys' do | ||||
|           let(:patterns) { %w[key-test1 key-test2] } | ||||
| 
 | ||||
|           it 'deletes keys for each pattern separatelly' do | ||||
|             expect_any_instance_of(Redis).to receive(:del).with(patterns.first).once # rubocop:disable RSpec/AnyInstanceOf | ||||
|             expect_any_instance_of(Redis).to receive(:del).with(patterns.last).once # rubocop:disable RSpec/AnyInstanceOf | ||||
| 
 | ||||
|             subject.execute | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'with cache_keys over batch size' do | ||||
|           let(:patterns) { %w[key-test*] } | ||||
| 
 | ||||
|           it 'deletes matched keys in batches' do | ||||
|             # redis scan returns the values in random order so just checking it is being called twice meaning | ||||
|             # scan returned results in 2 batches, which is what we expect | ||||
|             key_like = start_with('key-test') | ||||
|             expect_any_instance_of(Redis).to receive(:del).with(key_like, key_like).twice # rubocop:disable RSpec/AnyInstanceOf | ||||
| 
 | ||||
|             subject.execute | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| def write_to_cache | ||||
|   Gitlab::Redis::Cache.with do |redis| | ||||
|     cache_keys.each_with_index do |cache_key, index| | ||||
|       redis.set(cache_key, index) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -1,94 +0,0 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Gitlab::Cleanup::Redis::DescriptionTemplatesCacheKeysPatternBuilder, :clean_gitlab_redis_cache do | ||||
|   subject { described_class.new(project_ids).execute } | ||||
| 
 | ||||
|   describe 'execute' do | ||||
|     context 'when build pattern for all description templates' do | ||||
|       RSpec.shared_examples 'all issue and merge request templates pattern' do | ||||
|         it 'builds pattern to remove all issue and merge request templates keys' do | ||||
|           expect(subject.count).to eq(2) | ||||
|           expect(subject).to match_array(%W[ | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:issue_template_names_hash:* | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:merge_request_template_names_hash:* | ||||
|           ]) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'with project_ids == :all' do | ||||
|         let(:project_ids) { :all } | ||||
| 
 | ||||
|         it_behaves_like 'all issue and merge request templates pattern' | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with project_ids' do | ||||
|       let_it_be(:project1) { create(:project, :repository) } | ||||
|       let_it_be(:project2) { create(:project, :repository) } | ||||
| 
 | ||||
|       context 'with nil project_ids' do | ||||
|         let(:project_ids) { nil } | ||||
| 
 | ||||
|         specify { expect { subject }.to raise_error(ArgumentError, 'project_ids can either be an array of project IDs or :all') } | ||||
|       end | ||||
| 
 | ||||
|       context 'with project_ids as string' do | ||||
|         let(:project_ids) { '1' } | ||||
| 
 | ||||
|         specify { expect { subject }.to raise_error(ArgumentError, 'project_ids can either be an array of project IDs or :all') } | ||||
|       end | ||||
| 
 | ||||
|       context 'with invalid project_ids as array of strings' do | ||||
|         let(:project_ids) { %w[a b] } | ||||
| 
 | ||||
|         specify { expect { subject }.to raise_error(ArgumentError, 'Invalid Project ID. Please ensure all passed in project ids values are valid integer project ids.') } | ||||
|       end | ||||
| 
 | ||||
|       context 'with non existent project id' do | ||||
|         let(:project_ids) { [non_existing_record_id] } | ||||
| 
 | ||||
|         it 'no patterns are built' do | ||||
|           expect(subject.count).to eq(0) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'with one project_id' do | ||||
|         let(:project_ids) { [project1.id] } | ||||
| 
 | ||||
|         it 'builds patterns for the project' do | ||||
|           expect(subject.count).to eq(2) | ||||
|           expect(subject).to match_array(%W[ | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:issue_template_names_hash:#{project1.full_path}:#{project1.id} | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:merge_request_template_names_hash:#{project1.full_path}:#{project1.id} | ||||
|           ]) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'with many project_ids' do | ||||
|         let(:project_ids) { [project1.id, project2.id] } | ||||
| 
 | ||||
|         RSpec.shared_examples 'builds patterns for the given projects' do | ||||
|           it 'builds patterns for the given projects' do | ||||
|             expect(subject.count).to eq(4) | ||||
|             expect(subject).to match_array(%W[ | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:issue_template_names_hash:#{project1.full_path}:#{project1.id} | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:merge_request_template_names_hash:#{project1.full_path}:#{project1.id} | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:issue_template_names_hash:#{project2.full_path}:#{project2.id} | ||||
|             #{Gitlab::Redis::Cache::CACHE_NAMESPACE}:merge_request_template_names_hash:#{project2.full_path}:#{project2.id} | ||||
|             ]) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         it_behaves_like 'builds patterns for the given projects' | ||||
| 
 | ||||
|         context 'with project_ids as string' do | ||||
|           let(:project_ids) { [project1.id.to_s, project2.id.to_s] } | ||||
| 
 | ||||
|           it_behaves_like 'builds patterns for the given projects' | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -152,9 +152,13 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do | |||
|       expect(Gitlab::AppJsonLogger).to( | ||||
|         receive(:info) | ||||
|           .with(event: 'authorized_projects_refresh', | ||||
|                 user_id: user.id, | ||||
|                 'authorized_projects_refresh.source': source, | ||||
|                 'authorized_projects_refresh.rows_deleted': 0, | ||||
|                 'authorized_projects_refresh.rows_added': 1)) | ||||
|                 'authorized_projects_refresh.rows_deleted_count': 0, | ||||
|                 'authorized_projects_refresh.rows_added_count': 1, | ||||
|                 'authorized_projects_refresh.rows_deleted_slice': [], | ||||
|                 'authorized_projects_refresh.rows_added_slice': [[user.id, project.id, Gitlab::Access::MAINTAINER]]) | ||||
|       ) | ||||
| 
 | ||||
|       service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MAINTAINER]]) | ||||
|     end | ||||
|  |  | |||
|  | @ -41,10 +41,6 @@ module Spec | |||
|             find_row(user.name) | ||||
|           end | ||||
| 
 | ||||
|           def find_username_row(user) | ||||
|             find_row(user.username) | ||||
|           end | ||||
| 
 | ||||
|           def find_invited_member_row(email) | ||||
|             find_row(email) | ||||
|           end | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| require 'rake_helper' | ||||
| 
 | ||||
| RSpec.describe 'clearing redis cache', :clean_gitlab_redis_cache do | ||||
| RSpec.describe 'clearing redis cache' do | ||||
|   before do | ||||
|     Rake.application.rake_require 'tasks/cache' | ||||
|   end | ||||
|  | @ -21,27 +21,4 @@ RSpec.describe 'clearing redis cache', :clean_gitlab_redis_cache do | |||
|       expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'invoking clear description templates cache rake task' do | ||||
|     using RSpec::Parameterized::TableSyntax | ||||
| 
 | ||||
|     before do | ||||
|       stub_env('project_ids', project_ids) if project_ids | ||||
|       service = double(:service, execute: true) | ||||
| 
 | ||||
|       expect(Gitlab::Cleanup::Redis::DescriptionTemplatesCacheKeysPatternBuilder).to receive(:new).with(expected_project_ids).and_return(service) | ||||
|       expect(Gitlab::Cleanup::Redis::BatchDeleteByPattern).to receive(:new).and_return(service) | ||||
|     end | ||||
| 
 | ||||
|     where(:project_ids, :expected_project_ids) do | ||||
|       nil                    | [] # this acts as no argument is being passed | ||||
|       '1'                    | %w[1] | ||||
|       '1, 2, 3'              | %w[1 2 3] | ||||
|       '1, 2, some-string, 3' | %w[1 2 some-string 3] | ||||
|     end | ||||
| 
 | ||||
|     with_them do | ||||
|       specify { run_rake_task('cache:clear:description_templates') } | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue