Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-05 06:09:26 +00:00
parent 4b4d338d32
commit 5092e9b37c
54 changed files with 2189 additions and 1202 deletions

View file

@ -7,14 +7,34 @@ module Resolvers
alias_method :project, :object
argument :iid, GraphQL::ID_TYPE,
required: true,
description: 'IID of the Pipeline, e.g., "1".'
required: false,
description: 'IID of the Pipeline. For example, "1".'
def resolve(iid:)
BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args|
finder = ::Ci::PipelinesFinder.new(project, context[:current_user], iids: iids)
argument :sha, GraphQL::STRING_TYPE,
required: false,
description: 'SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b".'
finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) }
def ready?(iid: nil, sha: nil)
unless iid.present? ^ sha.present?
raise Gitlab::Graphql::Errors::ArgumentError, 'Provide one of an IID or SHA'
end
super
end
def resolve(iid: nil, sha: nil)
if iid
BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args|
finder = ::Ci::PipelinesFinder.new(project, current_user, iids: iids)
finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) }
end
else
BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
finder = ::Ci::PipelinesFinder.new(project, current_user, shas: shas)
finder.execute.each { |pipeline| loader.call(pipeline.sha.to_s, pipeline) }
end
end
end
end

View file

@ -95,6 +95,9 @@ module Types
field :path, GraphQL::STRING_TYPE, null: true,
description: "Relative path to the pipeline's page."
field :commit_path, GraphQL::STRING_TYPE, null: true,
description: 'Path to the commit that triggered the pipeline.'
field :project, Types::ProjectType, null: true,
description: 'Project the pipeline belongs to.'
@ -109,6 +112,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
end
def commit_path
::Gitlab::Routing.url_helpers.project_commit_path(object.project, object.sha)
end
def path
::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object)
end

View file

@ -812,14 +812,15 @@ module Ci
end
def cache
cache = options[:cache]
cache = Array.wrap(options[:cache])
if cache && project.jobs_cache_index
cache = cache.merge(
key: "#{cache[:key]}-#{project.jobs_cache_index}")
if project.jobs_cache_index
cache = cache.map do |single_cache|
single_cache.merge(key: "#{single_cache[:key]}-#{project.jobs_cache_index}")
end
end
[cache]
cache
end
def credentials

View file

@ -0,0 +1,5 @@
---
title: Allow search for pipeline by SHA as well as IID via GraphQL
merge_request: 54471
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Adds ability to have multiple cache per job
merge_request: 53410
author:
type: changed

View file

@ -0,0 +1,8 @@
---
name: multiple_cache_per_job
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53410
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321877
milestone: '13.10'
type: development
group: group::pipeline authoring
default_enabled: false

View file

@ -201,4 +201,4 @@ successfully, you must replicate their data using some other means.
| [GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | No | |
| [CI Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/ci/pipeline_artifact.rb) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/238464) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Persists additional artifacts after a pipeline completes |
| [Dependency proxy images](../../../user/packages/dependency_proxy/index.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/259694) | No | No | Blocked on [Geo: Secondary Mimicry](https://gitlab.com/groups/gitlab-org/-/epics/1528). Note that replication of this cache is not needed for Disaster Recovery purposes because it can be recreated from external sources. |
| [Vulnerability Export](../../../user/application_security/vulnerability_report/#export-vulnerabilities) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/3111) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Not planned because they are ephemeral and sensitive. They can be regenerated on demand. |
| [Vulnerability Export](../../../user/application_security/vulnerability_report/#export-vulnerability-details) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/3111) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Not planned because they are ephemeral and sensitive. They can be regenerated on demand. |

File diff suppressed because it is too large Load diff

View file

@ -1890,7 +1890,7 @@ This endpoint:
merge requests).
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on
[Premium or higher](https://about.gitlab.com/pricing/) tiers, group
admins can [configure](../user/group/index.md#enabling-delayed-project-removal)
admins can [configure](../user/group/index.md#enable-delayed-project-removal)
projects within a group to be deleted after a delayed period. When enabled,
actual deletion happens after the number of days specified in the
[default deletion delay](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
@ -1898,7 +1898,7 @@ This endpoint:
WARNING:
The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935)
in GitLab 12.6 was changed to [Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382)
in GitLab 13.2, as discussed in [Enabling delayed project removal](../user/group/index.md#enabling-delayed-project-removal).
in GitLab 13.2, as discussed in [Enable delayed project removal](../user/group/index.md#enable-delayed-project-removal).
```plaintext
DELETE /projects/:id

View file

@ -2754,7 +2754,62 @@ URI-encoded `%2F`. A value made only of dots (`.`, `%2E`) is also forbidden.
You can specify a [fallback cache key](#fallback-cache-key) to use if the specified `cache:key` is not found.
##### Fallback cache key
##### Multiple caches
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32814) in GitLab 13.10.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-multiple-caches). **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can have a maximum of four caches:
```yaml
test-job:
stage: build
cache:
- key:
files:
- Gemfile.lock
paths:
- vendor/ruby
- key:
files:
- yarn.lock
paths:
- .yarn-cache/
script:
- bundle install --path=vendor
- yarn install --cache-folder .yarn-cache
- echo Run tests...
```
If multiple caches are combined with a [Fallback cache key](#fallback-cache-key),
the fallback is fetched multiple times if multiple caches are not found.
##### Enable or disable multiple caches **(FREE SELF)**
The multiple caches feature is under development and not ready for production use.
It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:multiple_cache_per_job)
```
To disable it:
```ruby
Feature.disable(:multiple_cache_per_job)
```
#### Fallback cache key
> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1534) in GitLab Runner 13.4.

View file

@ -14,7 +14,7 @@ This document lists the different implementations of CSV export in GitLab codeba
| Downloading | - Query and write data in batches to a temporary file.<br>- Loads the file into memory.<br>- Sends the file to the client. | - Report available immediately. | - Large amount of data might cause request timeout.<br>- Memory intensive.<br>- Request expires when user navigates to a different page. | [Export Chain of Custody Report](../user/compliance/compliance_dashboard/#chain-of-custody-report) |
| As email attachment | - Asynchronously process the query with background job.<br>- Email uses the export as an attachment. | - Asynchronous processing. | - Requires users use a different app (email) to download the CSV.<br>- Email providers may limit attachment size. | - [Export Issues](../user/project/issues/csv_export.md)<br>- [Export Merge Requests](../user/project/merge_requests/csv_export.md) |
| As downloadable link in email (*) | - Asynchronously process the query with background job.<br>- Email uses an export link. | - Asynchronous processing.<br>- Bypasses email provider attachment size limit. | - Requires users use a different app (email).<br>- Requires additional storage and cleanup. | [Export User Permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/1772) |
| Polling (non-persistent state) | - Asynchronously processes the query with the background job.<br>- Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.<br>- Automatically downloads to local machine on completion.<br>- In-app solution. | - Non-persistable request - request expires when user navigates to a different page.<br>- API is processed for each polling request. | [Export Vulnerabilities](../user/application_security/vulnerability_report/#export-vulnerabilities) |
| Polling (non-persistent state) | - Asynchronously processes the query with the background job.<br>- Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.<br>- Automatically downloads to local machine on completion.<br>- In-app solution. | - Non-persistable request - request expires when user navigates to a different page.<br>- API is processed for each polling request. | [Export Vulnerabilities](../user/application_security/vulnerability_report/#export-vulnerability-details) |
| Polling (persistent state) (*) | - Asynchronously processes the query with background job.<br>- Backend (BE) maintains the export state<br>- FE polls every few seconds to check status.<br>- FE shows 'Download link' when export is ready.<br>- User can download or regenerate a new report. | - Asynchronous processing.<br>- No database calls made during the polling requests (HTTP 304 status is returned until export status changes).<br>- Does not require user to stay on page until export is complete.<br>- In-app solution.<br>- Can be expanded into a generic CSV feature (such as dashboard / CSV API). | - Requires to maintain export states in DB.<br>- Does not automatically download the CSV export to local machine, requires users to click 'Download' button. | [Export Merge Commits Report](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43055) |
NOTE:

View file

@ -28,7 +28,7 @@ the tiers are no longer mentioned in GitLab documentation:
- [Managing group memberships via LDAP](../user/group/index.md#manage-group-memberships-via-ldap)
- [Member locking](../user/group/index.md#prevent-members-from-being-added-to-a-group)
- [Overriding user permissions](../user/group/index.md#override-user-permissions)
- [User contribution analysis](../user/group/index.md#user-contribution-analysis)
- [User contribution analytics](../user/group/contribution_analytics/index.md)
- [Kerberos integration](../integration/kerberos.md)
- Issue Boards:
- [Configurable issue boards](../user/project/issue_board.md#configurable-issue-boards)

View file

@ -79,7 +79,7 @@ The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org
[Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
Projects in a group (but not a personal namespace) can be deleted after a delayed period, by
[configuring in Group Settings](../../group/index.md#enabling-delayed-project-removal).
[configuring in Group Settings](../../group/index.md#enable-delayed-project-removal).
The default period is seven days, and can be changed. Setting this period to `0` enables immediate removal
of projects or groups.

View file

@ -5,42 +5,80 @@ group: Threat Insights
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# GitLab Vulnerability Reports **(ULTIMATE)**
# Vulnerability Report **(ULTIMATE)**
Each vulnerability report contains vulnerabilities from the scans of the most recent branch merged into the default branch.
The Vulnerability Report provides information about vulnerabilities from scans of the branch most
recently merged into the default branch. It is available at the instance, group, and project level.
The vulnerability reports display the total number of vulnerabilities by severity (for example,
Critical, High, Medium, Low, Info, Unknown). Below this, a table shows each vulnerability's detected date, status, severity, description, identifier, the scanner where it was detected, and activity (including related issues or available solutions). By default, the vulnerability report is filtered to display all detected and confirmed vulnerabilities.
The Vulnerability Report contains:
- Totals of vulnerabilities per severity level.
- Filters for common vulnerability attributes.
- Details of each vulnerability, presented in tabular layout.
The project-level Vulnerability Report also contains:
- A time stamp showing when it was updated, including a link to the latest pipeline.
- The number of failures that occurred in the most recent pipeline. Select the failure
notification to view the **Failed jobs** tab of the pipeline's page.
![Vulnerability Report](img/group_vulnerability_report_v13_9.png)
Clicking any vulnerability in the table takes you to its
[vulnerability details](../vulnerabilities) page to see more information on that vulnerability.
From the Vulnerability Report you can:
The **Activity** column indicates the number of issues that have been created for the vulnerability.
Hover over an **Activity** entry and select a link go to that issue.
- [Filter the list of vulnerabilities](#filter-the-list-of-vulnerabilities).
- [View more details about a vulnerability](#view-details-of-a-vulnerability).
- [View an issue raised for a vulnerability](#view-issues-raised-for-a-vulnerability).
- [Change the status of vulnerabilities](#change-status-of-vulnerabilities).
- [Export details of vulnerabilities](#export-vulnerability-details).
![Display attached issues](img/vulnerability_list_table_v13_9.png)
## Vulnerability filters
## Filter options
You can filter the vulnerabilities table by:
You can filter which vulnerabilities display by:
| Filter | Available options |
| Filter | Available options |
|:---------|:------------------|
| Status | Detected, Confirmed, Dismissed, Resolved |
| Severity | Critical, High, Medium, Low, Info, Unknown |
| Status | Detected, Confirmed, Dismissed, Resolved. |
| Severity | Critical, High, Medium, Low, Info, Unknown. |
| Scanner | [Available scanners](../index.md#security-scanning-tools). |
| Project | Projects configured in the Security Center settings, or all projects in the group for the group level report. This filter is not displayed on the project level vulnerability report. |
| Activity | Vulnerabilities with issues and vulnerabilities that are no longer detected in the default branch. |
| Project | For more details, see [Project filter](#project-filter). |
| Activity | For more details, see [Activity filter](#activity-filter). |
### Filter the list of vulnerabilities
To filter the list of vulnerabilities:
1. Select a filter.
1. Select values from the dropdown.
1. Repeat the above steps for each desired filter.
The vulnerability table is applied immediately. The vulnerability severity totals are also updated.
The filters' criteria are combined to show only vulnerabilities matching all criteria.
An exception to this behavior is the Activity filter. For more details about how it works, see
[Activity filter](#activity-filter).
### Project filter
The content of the Project filter depends on the current level:
| Level | Content of the Project filter |
|:---------------|:------------------------------|
| Instance level | Only projects you've [added to the instance-level Security Center](../security_dashboard/index.md#adding-projects-to-the-security-center). |
| Group level | All projects in the group. |
| Project level | Not applicable. |
### Activity filter
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259255) in GitLab 13.9
The Activity filter behaves differently from the other Vulnerability Report filters. The other filter options all OR together to show results from any vulnerability matching one of the filter criteria. With the Activity filter, the selected values form mutually exclusive sets to allow for precisely locating the desired vulnerability records. Additionally, not all options can be selected in combination. Selection behavior when using the Activity filter:
The Activity filter behaves differently from the other filters. The selected values form mutually
exclusive sets to allow for precisely locating the desired vulnerability records. Additionally, not
all options can be selected in combination.
| Activity Selection | Results Displayed |
Selection behavior when using the Activity filter:
| Activity selection | Results displayed |
|:------------------------------------|:------------------|
| All | Vulnerabilities with any Activity status (same as ignoring this filter). Selecting this will deselect any other Activity filter options. |
| No activity | Only vulnerabilities without either an associated Issue or that are no longer detected. Selecting this will deselect any other Activity filter options. |
@ -48,9 +86,21 @@ The Activity filter behaves differently from the other Vulnerability Report filt
| No longer detected | Only vulnerabilities that are no longer detected in the latest pipeline scan of the `default` branch. Does not include vulnerabilities with one or more associated issues. |
| With issues and No longer detected | Only vulnerabilities that have one or more associated issues and also are no longer detected in the latest pipeline scan of the `default` branch. |
Contents of the unfiltered vulnerability report can be exported using our [export feature](#export-vulnerabilities).
## View details of a vulnerability
You can also change the status of vulnerabilities in the table:
To view more details of a vulnerability, select the vulnerability's **Description**. The
[vulnerability's details](../vulnerabilities) page is opened.
## View issues raised for a vulnerability
The **Activity** column indicates the number of issues that have been created for the vulnerability.
Hover over an **Activity** entry and select a link go to that issue.
![Display attached issues](img/vulnerability_list_table_v13_9.png)
## Change status of vulnerabilities
To change the status of vulnerabilities in the table:
1. Select the checkbox for each vulnerability you want to update the status of.
1. In the dropdown that appears select the desired status, then select **Change status**.
@ -72,31 +122,37 @@ the **Failed jobs** tab of the pipeline page.
![Project Vulnerability Report](img/project_security_dashboard_v13_9.png)
## Export vulnerabilities
## Export vulnerability details
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213014) in the Security Center (previously known as the Instance Security Dashboard) and project-level Vulnerability Report (previously known as the Project Security Dashboard) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
> - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/213013) to the group-level Vulnerability Report in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
You can export all your vulnerabilities in CSV (comma separated values) format by clicking the
**{upload}** **Export** button located at top right of the Security Dashboard. When the report is
ready, the CSV report downloads to your local machine. The report contains all vulnerabilities for
the projects defined in the Security Dashboard, as filters don't apply to the export function.
You can export details of the vulnerabilities listed in the Vulnerability Report. The export format
is CSV (comma separated values). Note that all vulnerabilities are included because filters don't
apply to the export.
Fields included are:
- Group name
- Project name
- Scanner type
- Scanner name
- Status
- Vulnerability
- Details
- Additional information
- Severity
- [CVE](https://cve.mitre.org/) (Common Vulnerabilities and Exposures)
- [CWE](https://cwe.mitre.org/) (Common Weakness Enumeration)
- Other identifiers
### Export details in CSV format
To export details of all vulnerabilities listed in the Vulnerability Report, select **Export**.
The details are retrieved from the database, then the CSV file is downloaded to your local
computer.
NOTE:
It may take several minutes for the download to start if your project contains
thousands of vulnerabilities. Don't close the page until the download finishes.
The fields in the export include:
- Group Name
- Project Name
- Scanner Type
- Scanner Name
- Status
- Vulnerability
- Details
- Additional Information
- Severity
- [CVE](https://cve.mitre.org/) (Common Vulnerabilities and Exposures)
- [CWE](https://cwe.mitre.org/) (Common Weakness Enumeration)
- Other Identifiers

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -59,7 +59,7 @@ To create a group:
- Alphanumeric characters
- Emojis
- Underscores
- Dashes, dots, spaces and parenthesis (however, it cannot start with any of these characters)
- Dashes, dots, spaces, and parentheses (however, it cannot start with any of these characters)
For a list of words that cannot be used as group names, see [reserved names](../reserved_names.md).
@ -214,13 +214,9 @@ In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab adminis
There are two different ways to add a new project to a group:
- Select a group, and then click **New project**. You can then continue [creating your project](../../user/project/working_with_projects.md#create-a-project).
- While you are creating a project, select a group from the dropdown menu.
![New project](img/create_new_project_from_group_v13_6.png)
- While you are creating a project, select a group namespace
you've already created from the dropdown menu.
![Select group](img/select_group_dropdown.png)
![Select group](img/select_group_dropdown_13_10.png)
### Specify who can add projects to a group
@ -261,22 +257,11 @@ You can view the most recent actions taken in a group.
To view the activity feed in Atom format, select the
**RSS** (**{rss}**) icon.
## Transfer projects into groups
Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace).
## Share a project with a group
You can [share your projects with a group](../project/members/share_project_with_groups.md)
and give all group members access to the project at once.
Alternatively, you can [lock the sharing with group feature](#prevent-a-project-from-being-shared-with-groups).
## Share a group with another group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7.
Similar to how you [share a project with a group](#share-a-project-with-a-group),
Similar to how you [share a project with a group](../project/members/share_project_with_groups.md),
you can share a group with another group. Members get direct access
to the shared group. This is not valid for inherited members.
@ -301,7 +286,7 @@ Group links can be created by using either a CN or a filter. To create these gro
For more information on the administration of LDAP and group sync, refer to the [main LDAP documentation](../../administration/auth/ldap/index.md#group-sync).
NOTE:
If an LDAP user is a group member when LDAP Synchronization is added, and they are not part of the LDAP group, they are removed from the group.
When you add LDAP synchronization, if an LDAP user is a group member and they are not part of the LDAP group, they are removed from the group.
### Create group links via CN **(PREMIUM SELF)**
@ -311,7 +296,7 @@ To create group links via CN:
1. Select the **LDAP Server** for the link.
1. As the **Sync method**, select `LDAP Group cn`.
1. In the **LDAP Group cn** text input box, begin typing the CN of the group. There is a dropdown menu with matching CNs within the configured `group_base`. Select your CN from this list.
1. In the **LDAP Group cn** field, begin typing the CN of the group. There is a dropdown menu with matching CNs in the configured `group_base`. Select your CN from this list.
1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
1. Select the **Add Synchronization** button.
@ -329,7 +314,7 @@ To create group links via filter:
### Override user permissions **(PREMIUM SELF)**
LDAP user permissions can be manually overridden by an admin user. To override a user's permissions:
LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
1. Go to your group's **Members** page.
1. In the row for the user you are editing, select the pencil (**{pencil}**) icon.
@ -337,28 +322,16 @@ LDAP user permissions can be manually overridden by an admin user. To override a
Now you can edit the user's permissions from the **Members** page.
## Epics **(ULTIMATE)**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.2.
Epics let you manage your portfolio of projects more efficiently and with less
effort by tracking groups of issues that share a theme, across projects and
milestones.
[Learn more about Epics.](epics/index.md)
## Group wikis **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13195) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5.
Group wikis work the same way as [project wikis](../project/wiki/index.md), please refer to those docs for details on usage.
Group wikis work the same way as [project wikis](../project/wiki/index.md).
Group wikis can be edited by members with [Developer permissions](../../user/permissions.md#group-members-permissions)
and above.
Group wiki repositories can be moved through the [Group repository storage moves API](../../api/group_repository_storage_moves.md).
### Group wikis limitations
You can move group wiki repositories by using the [Group repository storage moves API](../../api/group_repository_storage_moves.md).
There are a few limitations compared to project wikis:
@ -366,29 +339,7 @@ There are a few limitations compared to project wikis:
- Group wikis are not included in global search and Geo replication.
- Changes to group wikis don't show up in the group's activity feed.
For updates, you can follow:
- [The epic tracking feature parity with project wikis](https://gitlab.com/groups/gitlab-org/-/epics/2782).
## Group Security Dashboard **(ULTIMATE)**
Get an overview of the vulnerabilities of all the projects in a group and its subgroups.
[Learn more about the Group Security Dashboard.](../application_security/security_dashboard/index.md)
## Insights **(ULTIMATE)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
Configure the Insights that matter for your groups or projects, allowing users
to explore data such as:
- Triage hygiene
- Issues created/closed per a given period
- Average time for merge requests to be merged
- Much more
[Learn more about Insights](insights/index.md).
For updates, follow [the epic that tracks feature parity with project wikis](https://gitlab.com/groups/gitlab-org/-/epics/2782).
## Transfer a group
@ -403,66 +354,66 @@ When transferring groups, note:
- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](../project/repository/index.md#redirects-when-changing-repository-paths).
- You can only transfer groups to groups you manage.
- You must update your local repositories to point to the new location.
- If the immediate parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will change to match the new parent group's visibility.
- If the immediate parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects change to match the new parent group's visibility.
- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this leaves the group without an owner. In this case, the user transferring the group becomes the group's owner.
- Transfers will fail if [packages](../packages/index.md) exist in any of the projects within the group, or in any of its subgroups.
- Transfers fail if [packages](../packages/index.md) exist in any of the projects in the group, or in any of its subgroups.
## Change a group's path
Changing a group's path (group URL) can have unintended side effects. Read
[how redirects will behave](../project/repository/index.md#redirects-when-changing-repository-paths)
before proceeding.
[how redirects behave](../project/repository/index.md#redirects-when-changing-repository-paths)
before you proceed.
If you are vacating the path so it can be claimed by another group or user,
you may need to rename the group too, since both names and paths must
If you are changing the path so it can be claimed by another group or user,
you may need to rename the group too. Both names and paths must
be unique.
To retain ownership of the original namespace and protect the URL redirects,
create a new group and transfer projects to it instead.
To change your group path (group URL):
1. Navigate to your group's **Settings > General** page.
1. Go to your group's **Settings > General** page.
1. Expand the **Path, transfer, remove** section.
1. Enter a new name under **Change group URL**.
1. Click **Change group URL**.
1. Under **Change group URL**, enter a new name.
1. Select **Change group URL**.
WARNING:
It is currently not possible to rename a namespace if it contains a
It is not possible to rename a namespace if it contains a
project with [Container Registry](../packages/container_registry/index.md) tags,
because the project cannot be moved.
NOTE:
If you want to retain ownership over the original namespace and
protect the URL redirects, then instead of changing a group's path or renaming a
username, you can create a new group and transfer projects to it.
## Use a custom name for the initial branch
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43290) in GitLab 13.6.
By default, when you create a new project in GitLab, the initial branch is called `master`.
For groups, a group owner can customize the initial branch name to something
else. This way, every new project created under that group from then on will start from the custom branch name rather than `master`. To do so:
else. This way, every new project created under that group from then on starts from the custom branch name rather than `master`.
1. Go to the **Group page > Settings > Repository** and expand **Default initial
branch name**.
To use a custom name for the initial branch:
1. Go to the group's **Settings > Repository** page.
1. Expand the **Default initial branch name** section.
1. Change the default initial branch to a custom name of your choice.
1. **Save Changes**.
1. Select **Save changes**.
## Remove a group
To remove a group and its contents:
1. Navigate to your group's **Settings > General** page.
1. Go to your group's **Settings > General** page.
1. Expand the **Path, transfer, remove** section.
1. In the Remove group section, click the **Remove group** button.
1. Confirm the action when asked to.
1. In the Remove group section, select **Remove group**.
1. Confirm the action.
This action either:
This action removes the group. It also adds a background job to delete all projects in the group.
- Removes the group, and also queues a background job to delete all projects in that group.
- Since [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [Premium](https://about.gitlab.com/pricing/premium/) or higher tiers, this action adds a background job to mark a group for deletion. By default, the job schedules the deletion 7 days in the future. You can modify this waiting period through the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
Specifically:
Since [GitLab 13.6](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion leaves or is otherwise removed from the group before the
actual deletion happens, the job is cancelled, and the group is no longer scheduled for deletion.
- In [GitLab 12.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [Premium](https://about.gitlab.com/pricing/premium/) or higher tiers, this action adds a background job to mark a group for deletion. By default, the job schedules the deletion 7 days in the future. You can modify this waiting period through the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
- In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the
deletion happens, the job is cancelled, and the group is no longer scheduled for deletion.
## Restore a group **(PREMIUM)**
@ -470,15 +421,9 @@ actual deletion happens, the job is cancelled, and the group is no longer schedu
To restore a group that is marked for deletion:
1. Navigate to your group's **Settings > General** page.
1. Go to your group's **Settings > General** page.
1. Expand the **Path, transfer, remove** section.
1. In the Restore group section, click the **Restore group** button.
## Enforce two-factor authentication for group members
Add a security layer to your group by
[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group)
for all group members.
1. In the Restore group section, select **Restore group**.
## Prevent a project from being shared with groups
@ -486,34 +431,33 @@ Prevent projects in a group from [sharing
a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
For example, let's say you have two distinct teams (Group A and Group B) working together in a project, and to inherit the group membership, you share the project between the
two groups A and B. **Share with group lock** prevents any project within
two groups A and B. **Share with group lock** prevents any project in
the group from being shared with another group,
guaranteeing that only the right group members have access to those projects.
To enable this feature, navigate to the group settings page. Select
**Share with group lock** and **Save the group**.
To prevent a project from being shared with groups:
![Checkbox for share with group lock](img/share_with_group_lock.png)
1. Go to the group's **Settings > General** page.
1. Select **Share with group lock**.
1. Select **Save the group**.
## Prevent members from being added to a group **(PREMIUM)**
Member lock lets a group owner prevent any new project membership to all of the
projects within a group, allowing tighter control over project membership.
projects in a group, allowing tighter control over project membership.
For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
enable Member lock to guarantee that project membership cannot be modified during that audit.
enable member lock to guarantee that project membership cannot be modified during the audit.
To enable this feature:
To prevent members from being added to a group:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Member lock**.
1. Click **Save changes**.
1. Go to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. Select **Member lock**.
1. Select **Save changes**.
![Checkbox for membership lock](img/member_lock.png)
This will disable the option for all users who previously had permissions to
operate project memberships, so no new users can be added. Furthermore, any
request to add a new user to a project through API will not be possible.
All users who previously had permissions can no longer add members to a group.
API requests to add a new user to a project are not possible.
## Restrict group access by IP address **(PREMIUM)**
@ -521,13 +465,12 @@ request to add a new user to a project through API will not be possible.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215410) to [GitLab Premium](https://about.gitlab.com/pricing/) in 13.1.
NOTE:
IP Access Restrictions are currently not functioning as expected on GitLab.com. If enabled,
users cannot perform Git operations through SSH, or access projects via the UI. Please
review the [following bug report](https://gitlab.com/gitlab-org/gitlab/-/issues/271673) for
more information.
IP access restrictions are not functioning as expected on GitLab.com. If enabled,
users cannot perform Git operations through SSH, or access projects in the UI.
For more information, [see this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673).
To make sure only people from within your organization can access particular
resources, you have the option to restrict access to groups and their
To ensure only people from your organization can access particular
resources, you can restrict access to groups and their
underlying subgroups, projects, issues, and so on, by IP address. This can help ensure that
particular content doesn't leave the premises, while not blocking off access to
the entire instance. IP access restrictions can only be configured at the group level.
@ -545,55 +488,48 @@ Restriction currently applies to:
To avoid accidental lock-out, admins and group owners are able to access
the group regardless of the IP restriction.
To enable this feature:
To restrict group access by IP address:
1. Navigate to the groups **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and enter IP address ranges into **Allow access to the following IP addresses** field.
1. Click **Save changes**.
1. Go to the groups **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. In the **Allow access to the following IP addresses** field, enter IP address ranges.
1. Select **Save changes**.
![Domain restriction by IP address](img/restrict-by-ip.gif)
## Restrict group access by domain **(PREMIUM)**
>- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
>- Support for specifying multiple email domains [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1
>- Support for specifying multiple email domains [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) added in GitLab 13.1.
You can restrict access to groups by allowing only users with email addresses in particular domains to be added to the group.
You can prevent users with email addresses in specific domains from being added to a group.
Add email domains you want to allow and users with emails from different domains won't be allowed to be added to this group.
To restrict group access by domain:
Some domains cannot be restricted. These are the most popular public email domains, such as:
- `gmail.com`
- `yahoo.com`
- `hotmail.com`
- `aol.com`
- `msn.com`
- `hotmail.co.uk`
- `hotmail.fr`
- `live.com`
- `outlook.com`
- `icloud.com`
To enable this feature:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and enter the domain names into **Restrict membership by email** field.
1. Click **Save changes**.
1. Go to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. In the **Restrict membership by email** field, enter the domain names.
1. Select **Save changes**.
![Domain restriction by email](img/restrict-by-email.gif)
This will enable the domain-checking for all new users added to the group from this moment on.
Any time you attempt to add a new user, they are compared against this list.
Some domains cannot be restricted. These are the most popular public email domains, such as:
- `gmail.com`, `yahoo.com`, `aol.com`, `icloud.com`
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
- `msn.com`, `live.com`, `outlook.com`
NOTE:
Domain restrictions only apply to groups and do not prevent users from being added as members of projects owned by the restricted group.
Domain restrictions apply to groups only. They do not prevent users from being added as members of projects owned by the restricted group.
## Group file templates **(PREMIUM)**
Group file templates allow you to share a set of templates for common file
Use group file templates to share a set of templates for common file
types with every project in a group. It is analogous to the
[instance template repository](../admin_area/settings/instance_template_repository.md)
feature, and the selected project should follow the same naming conventions as
[instance template repository](../admin_area/settings/instance_template_repository.md).
The selected project should follow the same naming conventions as
are documented on that page.
You can only choose projects in the group as the template source.
@ -601,18 +537,17 @@ This includes projects shared with the group, but it **excludes** projects in
subgroups or parent groups of the group being configured.
You can configure this feature for both subgroups and immediate parent groups. A project
in a subgroup will have access to the templates for that subgroup, as well as
in a subgroup has access to the templates for that subgroup, as well as
any immediate parent groups.
![Group file template dropdown](img/group_file_template_dropdown.png)
To enable group file templates:
To enable this feature, navigate to the group settings page, expand the
**Templates** section, choose a project to act as the template repository, and
**Save group**.
1. Go to the group settings page.
1. Expand the **Templates** section.
1. Choose a project to act as the template repository.
1. Select **Save group**.
![Group file template settings](img/group_file_template_settings.png)
To learn how to create templates for issues and merge requests, visit
To learn how to create templates for issues and merge requests, see
[Description templates](../project/description_templates.md).
### Group-level project templates **(PREMIUM)**
@ -620,19 +555,20 @@ To learn how to create templates for issues and merge requests, visit
Define project templates at a group level by setting a group as the template source.
[Learn more about group-level project templates](custom_project_templates.md).
## Disabling email notifications
## Disable email notifications
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23585) in GitLab 12.2.
You can disable all email notifications related to the group, which includes its subgroups and projects.
To enable this feature:
To disable email notifications:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
1. Click **Save changes**.
1. Go to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. Select **Disable email notifications**.
1. Select **Save changes**.
## Disabling group mentions
## Disable group mentions
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21301) in GitLab 12.6.
@ -643,28 +579,30 @@ Groups with disabled mentions are visualized accordingly in the autocompletion d
This is particularly helpful for groups with a large number of users.
To enable this feature:
To disable group mentions:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable group mentions**.
1. Click **Save changes**.
1. Go to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. Select **Disable group mentions**.
1. Select **Save changes**.
## Enabling delayed project removal **(PREMIUM)**
## Enable delayed project removal **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
By default, projects within a group are deleted immediately.
By default, projects in a group are deleted immediately.
Optionally, on [Premium](https://about.gitlab.com/pricing/) or higher tiers,
you can configure the projects within a group to be deleted after a delayed interval.
you can configure the projects in a group to be deleted after a delayed interval.
During this interval period, the projects will be in a read-only state and can be restored, if required.
The interval period defaults to 7 days, and can be modified by an admin in the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
During this interval period, the projects are in a read-only state and can be restored, if required.
The interval period defaults to 7 days, and can be modified by an administrator in the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
To enable delayed deletion of projects:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and check **Enable delayed project removal**.
1. Click **Save changes**.
1. Go to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. Check **Enable delayed project removal**.
1. Select **Save changes**.
NOTE:
The group setting for delayed deletion is not inherited by subgroups and has to be individually defined for each group.
@ -673,29 +611,19 @@ The group setting for delayed deletion is not inherited by subgroups and has to
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3.
By default, projects within a group can be forked.
By default, projects in a group can be forked.
Optionally, on [Premium](https://about.gitlab.com/pricing/) or higher tiers,
you can prevent the projects within a group from being forked outside of the current top-level group.
you can prevent the projects in a group from being forked outside of the current top-level group.
Previously this setting was available only for groups enforcing group managed account. This setting will be
removed from SAML setting page and migrated to group setting, but in the interim period of changes both of those settings will be taken into consideration, if even one is set to `true` then it will be assumed group does not allow forking projects outside.
To enable prevent project forking:
1. Navigate to the top-level group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and check **Prevent project forking outside current group**.
1. Click **Save changes**.
## Advanced settings
- **Projects**: View all projects within that group, add members to each project,
access each project's settings, and remove any project, all from the same screen.
- **Webhooks**: Configure [webhooks](../project/integrations/webhooks.md) for your group.
- **Kubernetes cluster integration**: Connect your GitLab group with [Kubernetes clusters](clusters/index.md).
- **Audit Events**: View [Audit Events](../../administration/audit_events.md#group-events)
for the group.
- **Pipelines quota**: Keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
- **Integrations**: Configure [integrations](../admin_area/settings/project_integration_management.md) for your group.
1. Go to the top-level group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section.
1. Check **Prevent project forking outside current group**.
1. Select **Save changes**.
## Group push rules **(PREMIUM)**
@ -703,60 +631,43 @@ To enable prevent project forking:
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
Group push rules allow group maintainers to set
[push rules](../../push_rules/push_rules.md) for newly created projects within the specific group.
[push rules](../../push_rules/push_rules.md) for newly created projects in the specific group.
To configure push rules for a group, navigate to **{push-rules}** on the group's
sidebar.
To configure push rules for a group:
When set, new subgroups have push rules set for them based on either:
1. Go to the groups's **Push Rules** page.
1. Select the settings you want.
1. Select **Save Push Rules**.
The group's new subgroups have push rules set for them based on either:
- The closest parent group with push rules defined.
- Push rules set at the instance level, if no parent groups have push rules defined.
## Maximum artifacts size **(FREE SELF)**
## Related topics
For information about setting a maximum artifact size for a group, see
[Maximum artifacts size](../admin_area/settings/continuous_integration.md#maximum-artifacts-size).
## User contribution analysis **(PREMIUM)**
With [GitLab Contribution Analytics](contribution_analytics/index.md),
you have an overview of the contributions (pushes, merge requests,
and issues) performed by your group members.
## Issue analytics **(PREMIUM)**
With [GitLab Issue Analytics](issues_analytics/index.md), you can see a bar chart of the number of issues created each month in your groups.
## Repositories analytics **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in GitLab 13.6.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/276003) in GitLab 13.7.
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can view overall activity of all projects with code coverage.
## Dependency Proxy
Use GitLab as a [dependency proxy](../packages/dependency_proxy/index.md) for upstream Docker images.
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
## DORA4 analytics overview **(ULTIMATE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291747) in GitLab [Ultimate](https://about.gitlab.com/pricing/) 13.9 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
Group details include the following analytics:
- Deployment Frequency
For more information, see [DORA4 Project Analytics API](../../api/dora4_group_analytics.md).
- [Maximum artifacts size](../admin_area/settings/continuous_integration.md#maximum-artifacts-size). **(FREE SELF)**
- [Repositories analytics](repositories_analytics/index.md): View overall activity of all projects with code coverage.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in GitLab 13.6. **(PREMIUM)**
- [Contribution analytics](contribution_analytics/index.md): View the contributions (pushes, merge requests,
and issues) of group members. **(PREMIUM)**
- [Issue analytics](issues_analytics/index.md): View a bar chart of your group's number of issues per month. **(PREMIUM)**
- Use GitLab as a [dependency proxy](../packages/dependency_proxy/index.md) for upstream Docker images.
- [DORA4 Project Analytics API](../../api/dora4_group_analytics.md): View deployment frequency analytics.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291747) in GitLab Ultimate 13.9 as a
[Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta). **(ULTIMATE SELF)**
- [Epics](epics/index.md): Track groups of issues that share a theme. **(ULTIMATE)**
- [Security Dashboard](../application_security/security_dashboard/index.md): View the vulnerabilities of all
the projects in a group and its subgroups. **(ULTIMATE)**
- [Insights](insights/index.md): Configure insights like triage hygiene, issues created/closed per a given period, and
average time for merge requests to be merged. **(ULTIMATE)**
- [Webhooks](../project/integrations/webhooks.md).
- [Kubernetes cluster integration](clusters/index.md).
- [Audit Events](../../administration/audit_events.md#group-events). **(PREMIUM)**
- [Pipelines quota](../admin_area/settings/continuous_integration.md): Keep track of the pipeline quota for the group.
- [Integrations](../admin_area/settings/project_integration_management.md).
- [Transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace).
- [Share a project with a group](../project/members/share_project_with_groups.md): Give all group members access to the project at once.
- [Lock the sharing with group feature](#prevent-a-project-from-being-shared-with-groups).
- [Enforce two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group): Enforce 2FA
for all group members.

View file

@ -266,7 +266,7 @@ This action:
- Deletes a project including all associated resources (issues, merge requests etc).
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium](https://about.gitlab.com/pricing/) or higher tiers,
group owners can [configure](../../group/index.md#enabling-delayed-project-removal) projects within a group
group owners can [configure](../../group/index.md#enable-delayed-project-removal) projects within a group
to be deleted after a delayed period.
When enabled, actual deletion happens after number of days
specified in [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).

View file

@ -212,7 +212,7 @@ To delete a project, first navigate to the home page for that project.
1. Click **Delete project**
1. Confirm this action by typing in the expected text.
Projects in personal namespaces are deleted immediately on request. For information on delayed deletion of projects in a group, please see [Enabling delayed project removal](../group/index.md#enabling-delayed-project-removal).
Projects in personal namespaces are deleted immediately on request. For information on delayed deletion of projects in a group, please see [Enable delayed project removal](../group/index.md#enable-delayed-project-removal).
## Project settings

View file

@ -7,52 +7,90 @@ module Gitlab
##
# Entry that represents a cache configuration
#
class Cache < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
class Cache < ::Gitlab::Config::Entry::Simplifiable
strategy :Caches, if: -> (config) { Feature.enabled?(:multiple_cache_per_job) }
strategy :Cache, if: -> (config) { Feature.disabled?(:multiple_cache_per_job) }
ALLOWED_KEYS = %i[key untracked paths when policy].freeze
ALLOWED_POLICY = %w[pull-push push pull].freeze
DEFAULT_POLICY = 'pull-push'
ALLOWED_WHEN = %w[on_success on_failure always].freeze
DEFAULT_WHEN = 'on_success'
class Caches < ::Gitlab::Config::Entry::ComposableArray
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
validates :policy,
inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' },
allow_blank: true
MULTIPLE_CACHE_LIMIT = 4
with_options allow_nil: true do
validates :when,
inclusion: {
in: ALLOWED_WHEN,
message: 'should be on_success, on_failure or always'
}
validations do
validates :config, presence: true
validate do
unless config.is_a?(Hash) || config.is_a?(Array)
errors.add(:config, 'can only be a Hash or an Array')
end
if config.is_a?(Array) && config.count > MULTIPLE_CACHE_LIMIT
errors.add(:config, "no more than #{MULTIPLE_CACHE_LIMIT} caches can be created")
end
end
end
def initialize(*args)
super
@key = nil
end
def composable_class
Entry::Cache::Cache
end
end
entry :key, Entry::Key,
description: 'Cache key used to define a cache affinity.'
class Cache < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
entry :untracked, ::Gitlab::Config::Entry::Boolean,
description: 'Cache all untracked files.'
ALLOWED_KEYS = %i[key untracked paths when policy].freeze
ALLOWED_POLICY = %w[pull-push push pull].freeze
DEFAULT_POLICY = 'pull-push'
ALLOWED_WHEN = %w[on_success on_failure always].freeze
DEFAULT_WHEN = 'on_success'
entry :paths, Entry::Paths,
description: 'Specify which paths should be cached across builds.'
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
validates :policy,
inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' },
allow_blank: true
attributes :policy, :when
with_options allow_nil: true do
validates :when,
inclusion: {
in: ALLOWED_WHEN,
message: 'should be on_success, on_failure or always'
}
end
end
def value
result = super
entry :key, Entry::Key,
description: 'Cache key used to define a cache affinity.'
result[:key] = key_value
result[:policy] = policy || DEFAULT_POLICY
# Use self.when to avoid conflict with reserved word
result[:when] = self.when || DEFAULT_WHEN
entry :untracked, ::Gitlab::Config::Entry::Boolean,
description: 'Cache all untracked files.'
result
entry :paths, Entry::Paths,
description: 'Specify which paths should be cached across builds.'
attributes :policy, :when
def value
result = super
result[:key] = key_value
result[:policy] = policy || DEFAULT_POLICY
# Use self.when to avoid conflict with reserved word
result[:when] = self.when || DEFAULT_WHEN
result
end
end
class UnknownStrategy < ::Gitlab::Config::Entry::Node
end
end
end

View file

@ -67,6 +67,10 @@ module Gitlab
def self.display_codequality_backend_comparison?(project)
::Feature.enabled?(:codequality_backend_comparison, project, default_enabled: :yaml)
end
def self.multiple_cache_per_job?
::Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml)
end
end
end
end

View file

@ -28,8 +28,16 @@ module Gitlab
.fabricate(attributes.delete(:except))
@rules = Gitlab::Ci::Build::Rules
.new(attributes.delete(:rules), default_when: 'on_success')
@cache = Seed::Build::Cache
.new(pipeline, attributes.delete(:cache))
if multiple_cache_per_job?
cache = Array.wrap(attributes.delete(:cache))
@cache = cache.map do |cache|
Seed::Build::Cache.new(pipeline, cache)
end
else
@cache = Seed::Build::Cache
.new(pipeline, attributes.delete(:cache))
end
end
def name
@ -197,7 +205,21 @@ module Gitlab
def cache_attributes
strong_memoize(:cache_attributes) do
@cache.build_attributes
if multiple_cache_per_job?
if @cache.empty?
{}
else
{ options: { cache: @cache.map(&:attributes) } }
end
else
@cache.build_attributes
end
end
end
def multiple_cache_per_job?
strong_memoize(:multiple_cache_per_job) do
::Gitlab::Ci::Features.multiple_cache_per_job?
end
end

View file

@ -18,18 +18,18 @@ module Gitlab
raise ArgumentError, "unknown cache keys: #{local_cache.keys}" if local_cache.any?
end
def build_attributes
def attributes
{
options: {
cache: {
key: key_string,
paths: @paths,
policy: @policy,
untracked: @untracked,
when: @when
}.compact.presence
}.compact
}
key: key_string,
paths: @paths,
policy: @policy,
untracked: @untracked,
when: @when
}.compact
end
def build_attributes
{ options: { cache: attributes.presence }.compact }
end
private

View file

@ -28,7 +28,7 @@ module Gitlab
end
def render_name_and_description(object)
content = "### #{object[:name]}\n"
content = "### `#{object[:name]}`\n"
if object[:description].present?
content += "\n#{object[:description]}"

View file

@ -95,6 +95,7 @@ module QA
autoload :Visibility, 'qa/resource/visibility'
autoload :ProjectSnippet, 'qa/resource/project_snippet'
autoload :Design, 'qa/resource/design'
autoload :RegistryRepository, 'qa/resource/registry_repository'
module KubernetesCluster
autoload :Base, 'qa/resource/kubernetes_cluster/base'

View file

@ -13,7 +13,7 @@ module QA
element :tag_delete_button
end
def has_image_repository?(name)
def has_registry_repository?(name)
find('a[data-testid="details-link"]', text: name)
end

View file

@ -151,6 +151,10 @@ module QA
"#{api_get_path}/runners"
end
def api_registry_repositories_path
"#{api_get_path}/registry/repositories"
end
def api_commits_path
"#{api_get_path}/repository/commits"
end
@ -256,6 +260,12 @@ module QA
parse_body(response)
end
def registry_repositories
response = get Runtime::API::Request.new(api_client, "#{api_registry_repositories_path}").url
parse_body(response)
end
def repository_branches
parse_body(get(Runtime::API::Request.new(api_client, api_repository_branches_path).url))
end

View file

@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class RegistryRepository < Base
attr_accessor :name,
:repository_id
attribute :project do
Project.fabricate_via_api! do |resource|
resource.name = 'project-with-registry'
resource.description = 'Project with Registry'
end
end
def initialize
@name = project.path_with_namespace
@repository_id = nil
end
def fabricate!
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def remove_via_api!
registry_repositories = project.registry_repositories
if registry_repositories && !registry_repositories.empty?
this_registry_repository = registry_repositories.find { |registry_repository| registry_repository[:path] == name }
@repository_id = this_registry_repository[:id]
QA::Runtime::Logger.debug("Deleting registry '#{name}'")
super
end
end
def api_delete_path
"/projects/#{project.id}/registry/repositories/#{@repository_id}"
end
def api_get_path
"/projects/#{project.id}/registry/repositories"
end
end
end
end

View file

@ -38,7 +38,11 @@ module QA
end.merge_via_api!
expect(merge_request[:state]).to eq('merged')
expect(project).not_to have_branch(branch)
# Retry in case the branch deletion takes more time to finish
QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 5) do
expect(project).not_to have_branch(branch)
end
end
end
end

View file

@ -10,6 +10,13 @@ module QA
end
end
let(:registry_repository) do
Resource::RegistryRepository.fabricate! do |repository|
repository.name = "#{project.path_with_namespace}"
repository.project = project
end
end
let!(:gitlab_ci_yaml) do
<<~YAML
build:
@ -26,6 +33,10 @@ module QA
YAML
end
after do
registry_repository&.remove_via_api!
end
it 'pushes project image to the container registry and deletes tag', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1699' do
Flow::Login.sign_in
project.visit!
@ -52,9 +63,9 @@ module QA
Page::Project::Menu.perform(&:go_to_container_registry)
Page::Project::Registry::Show.perform do |registry|
expect(registry).to have_image_repository(project.path_with_namespace)
expect(registry).to have_registry_repository(registry_repository.name)
registry.click_on_image(project.path_with_namespace)
registry.click_on_image(registry_repository.name)
expect(registry).to have_tag('master')
registry.click_delete

View file

@ -6,7 +6,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234') }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234', sha: 'sha') }
let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) }
@ -30,7 +30,15 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to eq(pipeline)
end
it 'keeps the queries under the threshold' do
it 'resolves pipeline for the passed sha' do
result = batch_sync do
resolve_pipeline(project, { sha: 'sha' })
end
expect(result).to eq(pipeline)
end
it 'keeps the queries under the threshold for iid' do
create(:ci_pipeline, project: project, iid: '1235')
control = ActiveRecord::QueryRecorder.new do
@ -45,6 +53,21 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
end.not_to exceed_query_limit(control)
end
it 'keeps the queries under the threshold for sha' do
create(:ci_pipeline, project: project, sha: 'sha2')
control = ActiveRecord::QueryRecorder.new do
batch_sync { resolve_pipeline(project, { sha: 'sha' }) }
end
expect do
batch_sync do
resolve_pipeline(project, { sha: 'sha' })
resolve_pipeline(project, { sha: 'sha2' })
end
end.not_to exceed_query_limit(control)
end
it 'does not resolve a pipeline outside the project' do
result = batch_sync do
resolve_pipeline(other_pipeline.project, { iid: '1234' })
@ -53,8 +76,14 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to be_nil
end
it 'errors when no iid is passed' do
expect { resolve_pipeline(project, {}) }.to raise_error(ArgumentError)
it 'errors when no iid or sha is passed' do
expect { resolve_pipeline(project, {}) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'errors when both iid and sha are passed' do
expect { resolve_pipeline(project, { iid: '1234', sha: 'sha' }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
context 'when the pipeline is a dangling pipeline' do

View file

@ -12,7 +12,7 @@ RSpec.describe Types::Ci::PipelineType do
id iid sha before_sha status detailed_status config_source duration
coverage created_at updated_at started_at finished_at committed_at
stages user retryable cancelable jobs source_job downstream
upstream path project active user_permissions warnings
upstream path project active user_permissions warnings commit_path
]
if Gitlab.ee?

View file

@ -7,227 +7,287 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
subject(:entry) { described_class.new(config) }
describe 'validations' do
context 'with multiple caches' do
before do
entry.compose!
end
context 'when entry config value is correct' do
let(:policy) { nil }
let(:key) { 'some key' }
let(:when_config) { nil }
describe '#valid?' do
context 'when configuration is valid with a single cache' do
let(:config) { { key: 'key', paths: ["logs/"], untracked: true } }
let(:config) do
{
key: key,
untracked: true,
paths: ['some/path/']
}.tap do |config|
config[:policy] = policy if policy
config[:when] = when_config if when_config
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#value' do
shared_examples 'hash key value' do
it 'returns hash value' do
expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success')
context 'when configuration is valid with multiple caches' do
let(:config) do
[
{ key: 'key', paths: ["logs/"], untracked: true },
{ key: 'key2', paths: ["logs/"], untracked: true },
{ key: 'key3', paths: ["logs/"], untracked: true }
]
end
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when configuration is not a Hash or Array' do
let(:config) { 'invalid' }
it 'is invalid' do
expect(entry).not_to be_valid
end
end
context 'when entry values contain more than four caches' do
let(:config) do
[
{ key: 'key', paths: ["logs/"], untracked: true },
{ key: 'key2', paths: ["logs/"], untracked: true },
{ key: 'key3', paths: ["logs/"], untracked: true },
{ key: 'key4', paths: ["logs/"], untracked: true },
{ key: 'key5', paths: ["logs/"], untracked: true }
]
end
it 'is invalid' do
expect(entry.errors).to eq(["caches config no more than 4 caches can be created"])
expect(entry).not_to be_valid
end
end
end
end
context 'with a single cache' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
describe 'validations' do
before do
entry.compose!
end
context 'when entry config value is correct' do
let(:policy) { nil }
let(:key) { 'some key' }
let(:when_config) { nil }
let(:config) do
{
key: key,
untracked: true,
paths: ['some/path/']
}.tap do |config|
config[:policy] = policy if policy
config[:when] = when_config if when_config
end
end
it_behaves_like 'hash key value'
context 'with files' do
let(:key) { { files: %w[a-file other-file] } }
describe '#value' do
shared_examples 'hash key value' do
it 'returns hash value' do
expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success')
end
end
it_behaves_like 'hash key value'
context 'with files' do
let(:key) { { files: %w[a-file other-file] } }
it_behaves_like 'hash key value'
end
context 'with files and prefix' do
let(:key) { { files: %w[a-file other-file], prefix: 'prefix-value' } }
it_behaves_like 'hash key value'
end
context 'with prefix' do
let(:key) { { prefix: 'prefix-value' } }
it 'key is nil' do
expect(entry.value).to match(a_hash_including(key: nil))
end
end
context 'with `policy`' do
where(:policy, :result) do
'pull-push' | 'pull-push'
'push' | 'push'
'pull' | 'pull'
'unknown' | 'unknown' # invalid
end
with_them do
it { expect(entry.value).to include(policy: result) }
end
end
context 'without `policy`' do
it 'assigns policy to default' do
expect(entry.value).to include(policy: 'pull-push')
end
end
context 'with `when`' do
where(:when_config, :result) do
'on_success' | 'on_success'
'on_failure' | 'on_failure'
'always' | 'always'
'unknown' | 'unknown' # invalid
end
with_them do
it { expect(entry.value).to include(when: result) }
end
end
context 'without `when`' do
it 'assigns when to default' do
expect(entry.value).to include(when: 'on_success')
end
end
end
context 'with files and prefix' do
let(:key) { { files: %w[a-file other-file], prefix: 'prefix-value' } }
describe '#valid?' do
it { is_expected.to be_valid }
it_behaves_like 'hash key value'
end
context 'with files' do
let(:key) { { files: %w[a-file other-file] } }
context 'with prefix' do
let(:key) { { prefix: 'prefix-value' } }
it 'key is nil' do
expect(entry.value).to match(a_hash_including(key: nil))
it { is_expected.to be_valid }
end
end
context 'with `policy`' do
where(:policy, :result) do
'pull-push' | 'pull-push'
'push' | 'push'
'pull' | 'pull'
'unknown' | 'unknown' # invalid
where(:policy, :valid) do
'pull-push' | true
'push' | true
'pull' | true
'unknown' | false
end
with_them do
it { expect(entry.value).to include(policy: result) }
end
end
context 'without `policy`' do
it 'assigns policy to default' do
expect(entry.value).to include(policy: 'pull-push')
it 'returns expected validity' do
expect(entry.valid?).to eq(valid)
end
end
end
context 'with `when`' do
where(:when_config, :result) do
'on_success' | 'on_success'
'on_failure' | 'on_failure'
'always' | 'always'
'unknown' | 'unknown' # invalid
where(:when_config, :valid) do
'on_success' | true
'on_failure' | true
'always' | true
'unknown' | false
end
with_them do
it { expect(entry.value).to include(when: result) }
it 'returns expected validity' do
expect(entry.valid?).to eq(valid)
end
end
end
context 'without `when`' do
it 'assigns when to default' do
expect(entry.value).to include(when: 'on_success')
context 'with key missing' do
let(:config) do
{ untracked: true,
paths: ['some/path/'] }
end
describe '#value' do
it 'sets key with the default' do
expect(entry.value[:key])
.to eq(Gitlab::Ci::Config::Entry::Key.default)
end
end
end
end
describe '#valid?' do
it { is_expected.to be_valid }
context 'when entry value is not correct' do
describe '#errors' do
subject { entry.errors }
context 'with files' do
let(:key) { { files: %w[a-file other-file] } }
context 'when is not a hash' do
let(:config) { 'ls' }
it { is_expected.to be_valid }
end
end
context 'with `policy`' do
where(:policy, :valid) do
'pull-push' | true
'push' | true
'pull' | true
'unknown' | false
end
with_them do
it 'returns expected validity' do
expect(entry.valid?).to eq(valid)
end
end
end
context 'with `when`' do
where(:when_config, :valid) do
'on_success' | true
'on_failure' | true
'always' | true
'unknown' | false
end
with_them do
it 'returns expected validity' do
expect(entry.valid?).to eq(valid)
end
end
end
context 'with key missing' do
let(:config) do
{ untracked: true,
paths: ['some/path/'] }
end
describe '#value' do
it 'sets key with the default' do
expect(entry.value[:key])
.to eq(Gitlab::Ci::Config::Entry::Key.default)
end
end
end
end
context 'when entry value is not correct' do
describe '#errors' do
subject { entry.errors }
context 'when is not a hash' do
let(:config) { 'ls' }
it 'reports errors with config value' do
is_expected.to include 'cache config should be a hash'
end
end
context 'when policy is unknown' do
let(:config) { { policy: 'unknown' } }
it 'reports error' do
is_expected.to include('cache policy should be pull-push, push, or pull')
end
end
context 'when `when` is unknown' do
let(:config) { { when: 'unknown' } }
it 'reports error' do
is_expected.to include('cache when should be on_success, on_failure or always')
end
end
context 'when descendants are invalid' do
context 'with invalid keys' do
let(:config) { { key: 1 } }
it 'reports error with descendants' do
is_expected.to include 'key should be a hash, a string or a symbol'
it 'reports errors with config value' do
is_expected.to include 'cache config should be a hash'
end
end
context 'with empty key' do
let(:config) { { key: {} } }
context 'when policy is unknown' do
let(:config) { { policy: 'unknown' } }
it 'reports error with descendants' do
is_expected.to include 'key config missing required keys: files'
it 'reports error' do
is_expected.to include('cache policy should be pull-push, push, or pull')
end
end
context 'with invalid files' do
let(:config) { { key: { files: 'a-file' } } }
context 'when `when` is unknown' do
let(:config) { { when: 'unknown' } }
it 'reports error with descendants' do
is_expected.to include 'key:files config should be an array of strings'
it 'reports error' do
is_expected.to include('cache when should be on_success, on_failure or always')
end
end
context 'with prefix without files' do
let(:config) { { key: { prefix: 'a-prefix' } } }
context 'when descendants are invalid' do
context 'with invalid keys' do
let(:config) { { key: 1 } }
it 'reports error with descendants' do
is_expected.to include 'key config missing required keys: files'
it 'reports error with descendants' do
is_expected.to include 'key should be a hash, a string or a symbol'
end
end
context 'with empty key' do
let(:config) { { key: {} } }
it 'reports error with descendants' do
is_expected.to include 'key config missing required keys: files'
end
end
context 'with invalid files' do
let(:config) { { key: { files: 'a-file' } } }
it 'reports error with descendants' do
is_expected.to include 'key:files config should be an array of strings'
end
end
context 'with prefix without files' do
let(:config) { { key: { prefix: 'a-prefix' } } }
it 'reports error with descendants' do
is_expected.to include 'key config missing required keys: files'
end
end
context 'when there is an unknown key present' do
let(:config) { { key: { unknown: 'a-file' } } }
it 'reports error with descendants' do
is_expected.to include 'key config contains unknown keys: unknown'
end
end
end
context 'when there is an unknown key present' do
let(:config) { { key: { unknown: 'a-file' } } }
let(:config) { { invalid: true } }
it 'reports error with descendants' do
is_expected.to include 'key config contains unknown keys: unknown'
is_expected.to include 'cache config contains unknown keys: invalid'
end
end
end
context 'when there is an unknown key present' do
let(:config) { { invalid: true } }
it 'reports error with descendants' do
is_expected.to include 'cache config contains unknown keys: invalid'
end
end
end
end
end

View file

@ -537,7 +537,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'overrides default config' do
expect(entry[:image].value).to eq(name: 'some_image')
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success'])
end
end
@ -552,7 +552,43 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'uses config from default entry' do
expect(entry[:image].value).to eq 'specified'
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success'])
end
end
context 'with multiple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
context 'when job config overrides default config' do
before do
entry.compose!(deps)
end
let(:config) do
{ script: 'rspec', image: 'some_image', cache: { key: 'test' } }
end
it 'overrides default config' do
expect(entry[:image].value).to eq(name: 'some_image')
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
end
end
context 'when job config does not override default config' do
before do
allow(default).to receive('[]').with(:image).and_return(specified)
entry.compose!(deps)
end
let(:config) { { script: 'ls', cache: { key: 'test' } } }
it 'uses config from default entry' do
expect(entry[:image].value).to eq 'specified'
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
end
end
end

View file

@ -126,49 +126,105 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release])
expect(root.jobs_value[:rspec]).to eq(
{ name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
expect(root.jobs_value[:spinach]).to eq(
{ name: :spinach,
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
expect(root.jobs_value[:release]).to eq(
{ name: :release,
stage: 'release',
before_script: [],
script: ["make changelog | tee release_changelog.txt"],
release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
image: { name: "ruby:2.7" },
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
after_script: [],
ignore: false,
scheduling_type: :stage }
stage: 'release',
before_script: [],
script: ["make changelog | tee release_changelog.txt"],
release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
image: { name: "ruby:2.7" },
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
after_script: [],
ignore: false,
scheduling_type: :stage }
)
end
end
context 'with multuple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
root.compose!
end
describe '#jobs_value' do
it 'returns jobs configuration' do
expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release])
expect(root.jobs_value[:rspec]).to eq(
{ name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
expect(root.jobs_value[:spinach]).to eq(
{ name: :spinach,
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
expect(root.jobs_value[:release]).to eq(
{ name: :release,
stage: 'release',
before_script: [],
script: ["make changelog | tee release_changelog.txt"],
release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
image: { name: "ruby:2.7" },
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
after_script: [],
ignore: false,
scheduling_type: :stage }
)
end
end
end
end
end
@ -187,6 +243,52 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } }
end
context 'with multiple_cache_per_job FF disabled' do
context 'when composed' do
before do
stub_feature_flags(multiple_cache_per_job: false)
root.compose!
end
describe '#errors' do
it 'has no errors' do
expect(root.errors).to be_empty
end
end
describe '#jobs_value' do
it 'returns jobs configuration' do
expect(root.jobs_value).to eq(
rspec: { name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage },
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'job' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
end
end
end
end
context 'when composed' do
before do
root.compose!
@ -202,29 +304,29 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
it 'returns jobs configuration' do
expect(root.jobs_value).to eq(
rspec: { name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage },
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage },
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'job' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'job' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
scheduling_type: :stage }
)
end
end
@ -265,7 +367,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
describe '#cache_value' do
it 'returns correct cache definition' do
expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success')
expect(root.cache_value).to eq([key: 'a', policy: 'pull-push', when: 'on_success'])
end
end
context 'with multiple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
root.compose!
end
describe '#cache_value' do
it 'returns correct cache definition' do
expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success')
end
end
end
end

View file

@ -9,8 +9,255 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
let(:processor) { described_class.new(pipeline, config) }
describe '#build_attributes' do
subject { processor.build_attributes }
context 'with multiple_cache_per_job ff disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
describe '#build_attributes' do
subject { processor.build_attributes }
context 'with cache:key' do
let(:config) do
{
key: 'a-key',
paths: ['vendor/ruby']
}
end
it { is_expected.to include(options: { cache: config }) }
end
context 'with cache:key as a symbol' do
let(:config) do
{
key: :a_key,
paths: ['vendor/ruby']
}
end
it { is_expected.to include(options: { cache: config.merge(key: "a_key") }) }
end
context 'with cache:key:files' do
shared_examples 'default key' do
let(:config) do
{ key: { files: files } }
end
it 'uses default key' do
expected = { options: { cache: { key: 'default' } } }
is_expected.to include(expected)
end
end
shared_examples 'version and gemfile files' do
let(:config) do
{
key: {
files: files
},
paths: ['vendor/ruby']
}
end
it 'builds a string key' do
expected = {
options: {
cache: {
key: '703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
end
context 'with existing files' do
let(:files) { ['VERSION', 'Gemfile.zip'] }
it_behaves_like 'version and gemfile files'
end
context 'with files starting with ./' do
let(:files) { ['Gemfile.zip', './VERSION'] }
it_behaves_like 'version and gemfile files'
end
context 'with files ending with /' do
let(:files) { ['Gemfile.zip/'] }
it_behaves_like 'default key'
end
context 'with new line in filenames' do
let(:files) { ["Gemfile.zip\nVERSION"] }
it_behaves_like 'default key'
end
context 'with missing files' do
let(:files) { ['project-gemfile.lock', ''] }
it_behaves_like 'default key'
end
context 'with directories' do
shared_examples 'foo/bar directory key' do
let(:config) do
{
key: {
files: files
}
}
end
it 'builds a string key' do
expected = {
options: {
cache: { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
}
}
is_expected.to include(expected)
end
end
context 'with directory' do
let(:files) { ['foo/bar'] }
it_behaves_like 'foo/bar directory key'
end
context 'with directory ending in slash' do
let(:files) { ['foo/bar/'] }
it_behaves_like 'foo/bar directory key'
end
context 'with directories ending in slash star' do
let(:files) { ['foo/bar/*'] }
it_behaves_like 'foo/bar directory key'
end
end
end
context 'with cache:key:prefix' do
context 'without files' do
let(:config) do
{
key: {
prefix: 'a-prefix'
},
paths: ['vendor/ruby']
}
end
it 'adds prefix to default key' do
expected = {
options: {
cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
end
context 'with existing files' do
let(:config) do
{
key: {
files: ['VERSION', 'Gemfile.zip'],
prefix: 'a-prefix'
},
paths: ['vendor/ruby']
}
end
it 'adds prefix key' do
expected = {
options: {
cache: {
key: 'a-prefix-703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
end
context 'with missing files' do
let(:config) do
{
key: {
files: ['project-gemfile.lock', ''],
prefix: 'a-prefix'
},
paths: ['vendor/ruby']
}
end
it 'adds prefix to default key' do
expected = {
options: {
cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
end
end
context 'with all cache option keys' do
let(:config) do
{
key: 'a-key',
paths: ['vendor/ruby'],
untracked: true,
policy: 'push',
when: 'on_success'
}
end
it { is_expected.to include(options: { cache: config }) }
end
context 'with unknown cache option keys' do
let(:config) do
{
key: 'a-key',
unknown_key: true
}
end
it { expect { subject }.to raise_error(ArgumentError, /unknown_key/) }
end
context 'with empty config' do
let(:config) { {} }
it { is_expected.to include(options: {}) }
end
end
end
describe '#attributes' do
subject { processor.attributes }
context 'with cache:key' do
let(:config) do
@ -20,7 +267,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
it { is_expected.to include(options: { cache: config }) }
it { is_expected.to include(config) }
end
context 'with cache:key as a symbol' do
@ -31,7 +278,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
it { is_expected.to include(options: { cache: config.merge(key: "a_key") }) }
it { is_expected.to include(config.merge(key: "a_key")) }
end
context 'with cache:key:files' do
@ -41,7 +288,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
end
it 'uses default key' do
expected = { options: { cache: { key: 'default' } } }
expected = { key: 'default' }
is_expected.to include(expected)
end
@ -59,13 +306,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'builds a string key' do
expected = {
options: {
cache: {
key: '703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
@ -112,11 +355,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
end
it 'builds a string key' do
expected = {
options: {
cache: { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
}
}
expected = { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
is_expected.to include(expected)
end
@ -155,13 +394,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix to default key' do
expected = {
options: {
cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
@ -180,13 +415,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix key' do
expected = {
options: {
cache: {
key: 'a-prefix-703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
@ -205,13 +436,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix to default key' do
expected = {
options: {
cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
}
}
is_expected.to include(expected)
end
@ -229,7 +456,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
it { is_expected.to include(options: { cache: config }) }
it { is_expected.to include(config) }
end
context 'with unknown cache option keys' do
@ -242,11 +469,5 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it { expect { subject }.to raise_error(ArgumentError, /unknown_key/) }
end
context 'with empty config' do
let(:config) { {} }
it { is_expected.to include(options: {}) }
end
end
end

View file

@ -87,86 +87,167 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
context 'with multiple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
context 'with cache:key' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: 'a-value'
}
}
end
it { is_expected.to include(options: { cache: { key: 'a-value' } }) }
end
context 'with cache:key:files' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
files: ['VERSION']
}
}
}
end
it 'includes cache options' do
cache_options = {
options: {
cache: { key: 'f155568ad0933d8358f66b846133614f76dd0ca4' }
}
}
is_expected.to include(cache_options)
end
end
context 'with cache:key:prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
prefix: 'something'
}
}
}
end
it { is_expected.to include(options: { cache: { key: 'something-default' } }) }
end
context 'with cache:key:files and prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
files: ['VERSION'],
prefix: 'something'
}
}
}
end
it 'includes cache options' do
cache_options = {
options: {
cache: { key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4' }
}
}
is_expected.to include(cache_options)
end
end
end
context 'with cache:key' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
cache: [{
key: 'a-value'
}
}]
}
end
it { is_expected.to include(options: { cache: { key: 'a-value' } }) }
end
it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
context 'with cache:key:files' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
files: ['VERSION']
context 'with cache:key:files' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: [{
key: {
files: ['VERSION']
}
}]
}
end
it 'includes cache options' do
cache_options = {
options: {
cache: [a_hash_including(key: 'f155568ad0933d8358f66b846133614f76dd0ca4')]
}
}
}
is_expected.to include(cache_options)
end
end
it 'includes cache options' do
cache_options = {
options: {
cache: {
key: 'f155568ad0933d8358f66b846133614f76dd0ca4'
}
context 'with cache:key:prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: [{
key: {
prefix: 'something'
}
}]
}
}
end
is_expected.to include(cache_options)
end
end
context 'with cache:key:prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
prefix: 'something'
}
}
}
it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
end
it { is_expected.to include(options: { cache: { key: 'something-default' } }) }
end
context 'with cache:key:files and prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: [{
key: {
files: ['VERSION'],
prefix: 'something'
}
}]
}
end
context 'with cache:key:files and prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: {
key: {
files: ['VERSION'],
prefix: 'something'
it 'includes cache options' do
cache_options = {
options: {
cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
}
}
}
end
it 'includes cache options' do
cache_options = {
options: {
cache: {
key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4'
}
}
}
is_expected.to include(cache_options)
is_expected.to include(cache_options)
end
end
end
@ -179,7 +260,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
}
end
it { is_expected.to include(options: {}) }
it { is_expected.to include({}) }
end
context 'with allow_failure' do
@ -296,7 +377,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'does not have environment' do
expect(subject).not_to be_has_environment
expect(subject.environment).to be_nil
expect(subject.metadata.expanded_environment_name).to be_nil
expect(subject.metadata).to be_nil
expect(Environment.exists?(name: expected_environment_name)).to eq(false)
end
end

View file

@ -1368,6 +1368,155 @@ module Gitlab
end
end
context 'with multiple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
describe 'cache' do
context 'when cache definition has unknown keys' do
let(:config) do
YAML.dump(
{ cache: { untracked: true, invalid: 'key' },
rspec: { script: 'rspec' } })
end
it_behaves_like 'returns errors', 'cache config contains unknown keys: invalid'
end
it "returns cache when defined globally" do
config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
rspec: {
script: "rspec"
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
)
end
it "returns cache when defined in default context" do
config = YAML.dump(
{
default: {
cache: { paths: ["logs/", "binaries/"], untracked: true, key: { files: ['file'] } }
},
rspec: {
script: "rspec"
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
)
end
it 'returns cache key when defined in a job' do
config = YAML.dump({
rspec: {
cache: { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' },
script: 'rspec'
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
paths: ['logs/', 'binaries/'],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
)
end
it 'returns cache files' do
config = YAML.dump(
rspec: {
cache: {
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'] }
},
script: 'rspec'
}
)
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
)
end
it 'returns cache files with prefix' do
config = YAML.dump(
rspec: {
cache: {
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'], prefix: 'prefix' }
},
script: 'rspec'
}
)
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'], prefix: 'prefix' },
policy: 'pull-push',
when: 'on_success'
)
end
it "overwrite cache when defined for a job and globally" do
config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: {
script: "rspec",
cache: { paths: ["test/"], untracked: false, key: 'local' }
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
paths: ["test/"],
untracked: false,
key: 'local',
policy: 'pull-push',
when: 'on_success'
)
end
end
end
describe 'cache' do
context 'when cache definition has unknown keys' do
let(:config) do
@ -1381,22 +1530,22 @@ module Gitlab
it "returns cache when defined globally" do
config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
rspec: {
script: "rspec"
}
})
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
rspec: {
script: "rspec"
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["logs/", "binaries/"],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
)
])
end
it "returns cache when defined in default context" do
@ -1413,32 +1562,46 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["logs/", "binaries/"],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
)
])
end
it 'returns cache key when defined in a job' do
it 'returns cache key/s when defined in a job' do
config = YAML.dump({
rspec: {
cache: { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' },
script: 'rspec'
}
})
rspec: {
cache: [
{ paths: ['binaries/'], untracked: true, key: 'keya' },
{ paths: ['logs/', 'binaries/'], untracked: true, key: 'key' }
],
script: 'rspec'
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
paths: ['logs/', 'binaries/'],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
[
{
paths: ['binaries/'],
untracked: true,
key: 'keya',
policy: 'pull-push',
when: 'on_success'
},
{
paths: ['logs/', 'binaries/'],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
}
]
)
end
@ -1446,10 +1609,10 @@ module Gitlab
config = YAML.dump(
rspec: {
cache: {
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'] }
},
paths: ['binaries/'],
untracked: true,
key: { files: ['file'] }
},
script: 'rspec'
}
)
@ -1457,13 +1620,13 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
paths: ['logs/', 'binaries/'],
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq([
paths: ['binaries/'],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
)
])
end
it 'returns cache files with prefix' do
@ -1481,34 +1644,34 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq([
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'], prefix: 'prefix' },
policy: 'pull-push',
when: 'on_success'
)
])
end
it "overwrite cache when defined for a job and globally" do
config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: {
script: "rspec",
cache: { paths: ["test/"], untracked: false, key: 'local' }
}
})
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: {
script: "rspec",
cache: { paths: ["test/"], untracked: false, key: 'local' }
}
})
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["test/"],
untracked: false,
key: 'local',
policy: 'pull-push',
when: 'on_success'
)
])
end
end

View file

@ -40,7 +40,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
specify do
expectation = <<~DOC
### ArrayTest
### `ArrayTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
@ -53,7 +53,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
context 'query generation' do
let(:expectation) do
<<~DOC
### foo
### `foo`
List of objects.
@ -91,7 +91,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
specify do
expectation = <<~DOC
### OrderingTest
### `OrderingTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
@ -114,7 +114,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
specify do
expectation = <<~DOC
### DeprecatedTest
### `DeprecatedTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
@ -143,7 +143,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
specify do
expectation = <<~DOC
### MyEnum
### `MyEnum`
| Value | Description |
| ----- | ----------- |

View file

@ -817,7 +817,48 @@ RSpec.describe Ci::Build do
end
describe '#cache' do
let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } }
let(:options) do
{ cache: [{ key: "key", paths: ["public"], policy: "pull-push" }] }
end
context 'with multiple_cache_per_job FF disabled' do
before do
stub_feature_flags(multiple_cache_per_job: false)
end
let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } }
subject { build.cache }
context 'when build has cache' do
before do
allow(build).to receive(:options).and_return(options)
end
context 'when project has jobs_cache_index' do
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
end
it { is_expected.to be_an(Array).and all(include(key: "key-1")) }
end
context 'when project does not have jobs_cache_index' do
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
end
it { is_expected.to eq([options[:cache]]) }
end
end
context 'when build does not have cache' do
before do
allow(build).to receive(:options).and_return({})
end
it { is_expected.to eq([]) }
end
end
subject { build.cache }
@ -826,6 +867,21 @@ RSpec.describe Ci::Build do
allow(build).to receive(:options).and_return(options)
end
context 'when build has multiple caches' do
let(:options) do
{ cache: [
{ key: "key", paths: ["public"], policy: "pull-push" },
{ key: "key2", paths: ["public"], policy: "pull-push" }
] }
end
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
end
it { is_expected.to match([a_hash_including(key: "key-1"), a_hash_including(key: "key2-1")]) }
end
context 'when project has jobs_cache_index' do
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
@ -839,7 +895,7 @@ RSpec.describe Ci::Build do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
end
it { is_expected.to eq([options[:cache]]) }
it { is_expected.to eq(options[:cache]) }
end
end
@ -848,7 +904,7 @@ RSpec.describe Ci::Build do
allow(build).to receive(:options).and_return({})
end
it { is_expected.to eq([nil]) }
it { is_expected.to be_empty }
end
end

View file

@ -13,7 +13,7 @@ RSpec.describe 'container repository details' do
graphql_query_for(
'containerRepository',
{ id: container_repository_global_id },
all_graphql_fields_for('ContainerRepositoryDetails')
all_graphql_fields_for('ContainerRepositoryDetails', excluded: ['pipeline'])
)
end

View file

@ -18,7 +18,7 @@ RSpec.describe 'getting container repositories in a group' do
<<~GQL
edges {
node {
#{all_graphql_fields_for('container_repositories'.classify)}
#{all_graphql_fields_for('container_repositories'.classify, max_depth: 1)}
}
}
GQL

View file

@ -23,7 +23,7 @@ RSpec.describe 'getting projects' do
projects(includeSubgroups: #{include_subgroups}) {
edges {
node {
#{all_graphql_fields_for('Project')}
#{all_graphql_fields_for('Project', max_depth: 1)}
}
}
}

View file

@ -15,7 +15,7 @@ RSpec.describe 'package details' do
end
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration] }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline] }
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)

View file

@ -16,7 +16,7 @@ RSpec.describe 'getting container repositories in a project' do
<<~GQL
edges {
node {
#{all_graphql_fields_for('container_repositories'.classify)}
#{all_graphql_fields_for('container_repositories'.classify, excluded: ['pipeline'])}
}
}
GQL

View file

@ -34,7 +34,7 @@ RSpec.describe 'getting notes for a merge request' do
notes {
edges {
node {
#{all_graphql_fields_for('Note')}
#{all_graphql_fields_for('Note', excluded: ['pipeline'])}
}
}
}

View file

@ -9,7 +9,7 @@ RSpec.describe 'getting merge request information nested in a project' do
let(:current_user) { create(:user) }
let(:merge_request_graphql_data) { graphql_data['project']['mergeRequest'] }
let!(:merge_request) { create(:merge_request, source_project: project) }
let(:mr_fields) { all_graphql_fields_for('MergeRequest') }
let(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: ['pipeline']) }
let(:query) do
graphql_query_for(

View file

@ -11,10 +11,14 @@ RSpec.describe 'getting pipeline information nested in a project' do
let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] }
let!(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('pipeline', iid: pipeline.iid.to_s)
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
configSource
}
}
}
)
end

View file

@ -192,7 +192,17 @@ module TestEnv
end
def gitaly_dir
File.dirname(gitaly_socket_path)
socket_path = gitaly_socket_path
socket_path = File.expand_path(gitaly_socket_path) if expand_path?
File.dirname(socket_path)
end
# Linux fails with "bind: invalid argument" if a UNIX socket path exceeds 108 characters:
# https://github.com/golang/go/issues/6895. We use absolute paths in CI to ensure
# that changes in the current working directory don't affect GRPC reconnections.
def expand_path?
!!ENV['CI']
end
def start_gitaly(gitaly_dir)

View file

@ -17,7 +17,7 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
notes {
edges {
node {
#{all_graphql_fields_for('Note')}
#{all_graphql_fields_for('Note', max_depth: 1)}
}
}
}