Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-26 00:09:13 +00:00
parent 3e78ae6ec1
commit 89a4f4761d
44 changed files with 1040 additions and 923 deletions

View File

@ -6,7 +6,7 @@ export default class FilteredSearchTokenizer {
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(
`(${allowedKeys.join('|')}):(=|!=)?([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
`(${allowedKeys.join('|')}):(=|!=)?([~%@&]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
'g',
);
const tokens = [];
@ -15,17 +15,19 @@ export default class FilteredSearchTokenizer {
const searchToken =
input
.replace(tokenRegex, (match, key, operator, symbol, v1, v2, v3) => {
const prefixedTokens = ['~', '%', '@', '&'];
const comparisonTokens = ['!=', '='];
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
let tokenOperator = operator;
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
if (prefixedTokens.includes(tokenValue)) {
tokenSymbol = tokenValue;
tokenValue = '';
}
if (tokenValue === '!=' || tokenValue === '=') {
if (comparisonTokens.includes(tokenValue)) {
tokenOperator = tokenValue;
tokenValue = '';
}

View File

@ -162,10 +162,6 @@ $mr-widget-min-height: 69px;
.btn {
font-size: $gl-font-size;
&[disabled] {
opacity: 0.3;
}
&.dropdown-toggle {
.fa {
color: inherit;

View File

@ -539,7 +539,6 @@ module Ci
.concat(job_variables)
.concat(environment_changed_page_variables)
.concat(persisted_environment_variables)
.concat(deploy_freeze_variables)
.to_runner_variables
end
end
@ -595,18 +594,6 @@ module Ci
end
end
def deploy_freeze_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless freeze_period?
variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true')
end
end
def freeze_period?
Ci::FreezePeriodStatus.new(project: project).execute
end
def dependency_variables
return [] if all_dependencies.empty?

View File

@ -604,6 +604,10 @@ module Ci
project.deployment_platform&.active?
end
def freeze_period?
Ci::FreezePeriodStatus.new(project: project).execute
end
def has_warnings?
number_of_warnings.positive?
end
@ -714,6 +718,7 @@ module Ci
end
variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period?
if external_pull_request_event? && external_pull_request
variables.concat(external_pull_request.predefined_variables)

View File

@ -0,0 +1,5 @@
---
title: Properly set CI_DEPLOY_FREEZE variable in pipelines
merge_request: 35226
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix styling bug for disabled merge button
merge_request: 35365
author:
type: fixed

View File

@ -2,7 +2,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
You can use the Freeze Periods API to manipulate GitLab's [Freeze Period](../user/project/releases/index.md#set-a-deploy-freeze) entries.
You can use the Freeze Periods API to manipulate GitLab's [Freeze Period](../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze) entries.
## Permissions and security

View File

@ -7,7 +7,7 @@ that help maintain deployment security and stability.
You can:
- [Restrict write-access to a critical environment](#restrict-write-access-to-a-critical-environment)
- [Restrict deployments for a particular period](#restrict-deployments-for-a-particular-period)
- [Prevent deployments during deploy freeze windows](#prevent-deployments-during-deploy-freeze-windows)
If you are using a continuous deployment workflow and want to ensure that concurrent deployments to the same environment do not happen, you should enable the following options:
@ -77,10 +77,10 @@ The improved pipeline flow **after** enabling Skip outdated deployment jobs:
1. The `deploy` job in Pipeline-B finishes first, and deploys the newer code.
1. The `deploy` job in Pipeline-A is automatically cancelled, so that it doesn't overwrite the deployment from the newer pipeline.
## Restrict deployments for a particular period
## Prevent deployments during deploy freeze windows
If you want to prevent deployments for a particular period, for example during a planned
vacation period when most employees are out, you can set up a [Deploy Freeze](../../user/project/releases/index.md#set-a-deploy-freeze).
vacation period when most employees are out, you can set up a [Deploy Freeze](../../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze).
During a deploy freeze period, no deployment can be executed. This is helpful to
ensure that deployments do not happen unexpectedly.

View File

@ -431,7 +431,7 @@ in GitLab that helps maintain deployment security and stability.
- [Restrict write-access to a critical environment](deployment_safety.md#restrict-write-access-to-a-critical-environment)
- [Limit the job-concurrency for deployment jobs](deployment_safety.md#ensure-only-one-deployment-job-runs-at-a-time)
- [Skip outdated deployment jobs](deployment_safety.md#skip-outdated-deployment-jobs)
- [Restrict deployments for a particular period](deployment_safety.md#restrict-deployments-for-a-particular-period)
- [Prevent deployments during deploy freeze windows](deployment_safety.md#prevent-deployments-during-deploy-freeze-windows)
### Complete example

View File

@ -62,8 +62,7 @@ We are building a [JenkinsFile Wrapper](https://gitlab.com/gitlab-org/jfr-contai
you to run a complete Jenkins instance inside of a GitLab job, including plugins. This can help ease the process
of transition, by letting you delay the migration of less urgent pipelines for a period of time.
If you are interested, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) to
If you are interested, you might be able to [help GitLab test the wrapper](https://gitlab.com/gitlab-org/gitlab/-/issues/215675).
If you are interested in helping GitLab test the wrapper, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) for instructions and to provide your feedback.
## Important product differences

View File

@ -29,7 +29,7 @@ Similarly, milestones can be used as releases. To do so:
1. Set the milestone title to the version of your release, such as `Version 9.4`.
1. Add an issue to your release by associating the desired milestone from the issue's right-hand sidebar.
Additionally, you can integrate milestones with GitLab's [Releases feature](../releases/index.md#releases-associated-with-milestones).
Additionally, you can integrate milestones with GitLab's [Releases feature](../releases/index.md#associate-milestones-with-a-release).
## Project milestones and group milestones

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -9,24 +9,204 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) in GitLab 11.7.
It is typical to create a [Git tag](../../../university/training/topics/tags.md) at
the moment of release to introduce a checkpoint in your source code
history, but in most cases your users will need compiled objects or other
assets output by your CI system to use them, not just the raw source
code.
To introduce a checkpoint in your source code history, you can assign a
[Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) at the moment of release.
However, in most cases, your users need more than just the raw source code.
They need compiled objects or other assets output by your CI/CD system.
GitLab's **Releases** are a way to track deliverables in your project. Consider them
a snapshot in time of the source, build output, artifacts, and other metadata
A GitLab *Release* is a snapshot of the source, build output, artifacts, and other metadata
associated with a released version of your code.
## Getting started with Releases
You can create a GitLab release on any branch. When you create a release:
Start by giving a [description](#release-description) to the Release and
including its [assets](#release-assets), as follows.
- GitLab automatically archives source code and associates it with the release.
- GitLab automatically creates a JSON file that lists everything in the release,
so you can compare and audit releases. This file is called [release evidence](#release-evidence).
- You can add release notes and a message for the tag associated with the release.
## Release versioning
After you create a release, you can [associate milestones with it](#associate-milestones-with-a-release),
and attach [release assets](#release-assets), like runbooks or packages.
Release versions are manually assigned by the user in the Release title. GitLab uses [Semantic Versioning](https://semver.org/) for our releases, and we recommend you do too. Use `(Major).(Minor).(Patch)`, as detailed in the [GitLab Policy for Versioning](../../../policy/maintenance.md#versioning).
## View releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36667) in GitLab 12.8.
To view a list of releases:
- Go to **Project overview > Releases**, or
- On the project's overview page, if at least one release exists, click the number of releases.
![Number of Releases](img/releases_count_v13_2.png "Incremental counter of Releases")
- On public projects, this number is visible to all users.
- On private projects, this number is visible to users with Reporter
[permissions](../../permissions.md#project-members-permissions) or higher.
## Create a release
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32812) in GitLab 12.9. Releases can be created directly in the GitLab UI.
NOTE: **Note:**
Only users with Developer permissions or higher can create releases.
Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
You can create a release in the user interface, or by using the
[Releases API](../../../api/releases/index.md#create-a-release).
We recommend using the API to add release notes as one of the last steps in your CI/CD release pipeline.
To create a new release through the GitLab UI:
1. Navigate to **Project overview > Releases** and click the **New release** button.
1. In the [**Tag name**](#tag-name) box, enter a name.
1. In the **Create from** list, select the branch or enter a tag or commit SHA.
1. In the **Message** box, enter a message associated with the tag.
1. Optionally, in the [**Release notes**](#release-notes-description)
field, enter the release's description. You can use Markdown and drag and drop files to this field.
- If you leave this field empty, only a tag will be created.
- If you populate it, both a tag and a release will be created.
1. Click **Create tag**.
If you created a release, you can view it at **Project overview > Releases**.
If you created a tag, you can view it at **Repository > Tags**.
You can now edit the release to [add milestones](#associate-milestones-with-a-release)
and [release assets](#release-assets).
### Schedule a future release
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
You can create a release ahead of time by using the [Releases API](../../../api/releases/index.md#upcoming-releases).
When you set a future `released_at` date, an **Upcoming Release** badge is displayed next to the
release tag. When the `released_at` date and time has passed, the badge is automatically removed.
![An upcoming release](img/upcoming_release_v12_7.png)
## Edit a release
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10.
NOTE: **Note:**
Only users with Developer permissions or higher can edit releases.
Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
To edit the details of a release:
1. Navigate to **Project overview > Releases**.
1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
1. On the **Edit Release** page, change the release's details.
1. Click **Save changes**.
You can edit the release title, notes, associated milestones, and asset links.
To change other release information, such as the tag or release date, use the
[Releases API](../../../api/releases/index.md#update-a-release).
## Add release notes to Git tags
If you have an existing Git tag, you can add release notes to it.
You can do this in the user interface, or by using the [Releases API](../../../api/releases/index.md).
We recommend using the API to add release notes as one of the last steps in your CI/CD release pipeline.
In the interface, to add release notes to a new Git tag:
1. Navigate to your project's **Repository > Tags**.
1. Click **New tag**.
1. In the **Release notes** field, enter the release's description.
You can use Markdown and drag and drop files to this field.
1. Click **Create tag**.
In the interface, to add release notes to an existing Git tag:
1. Navigate to your project's **Repository > Tags**.
1. Click **Edit release notes** (the pencil icon).
1. In the **Release notes** field, enter the release's description.
You can use Markdown in this field, and drag and drop files to it.
1. Click **Save changes**.
## Associate milestones with a release
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29020) in GitLab 12.5.
> - [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/39467) to edit milestones in the UI in GitLab 13.0.
You can associate a release with one or more [project milestones](../milestones/index.md#project-milestones-and-group-milestones).
You can do this in the user interface, or by including a `milestones` array in your request to
the [Releases API](../../../api/releases/index.md#create-a-release).
In the user interface, to associate milestones to a release:
1. Navigate to **Project overview > Releases**.
1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
1. From the **Milestones** list, select each milestone you want to associate. You can select multiple milestones.
1. Click **Save changes**.
On the **Project overview > Releases** page, the **Milestone** is listed in the top
section, along with statistics about the issues in the milestones.
![A Release with one associated milestone](img/release_with_milestone_v12_9.png)
Releases are also visible on the **Issues > Milestones** page, and when you click a milestone
on this page.
Here is an example of milestones with no releases, one release, and two releases, respectively.
![Milestones with and without Release associations](img/milestone_list_with_releases_v12_5.png)
## Get notified when a release is created
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4.
You can be notified by email when a new release is created for your project.
To subscribe to notifications for releases:
1. Navigate to **Project overview**.
1. Click **Notification setting** (the bell icon).
1. In the list, click **Custom**.
1. Select the **New release** check box.
1. Close the dialog box to save.
## Prevent unintentional releases by setting a deploy freeze
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
Prevent unintended production releases during a period of time you specify by
setting a [*deploy freeze* period](../../../ci/environments/deployment_safety.md).
Deploy freezes help reduce uncertainty and risk when automating deployments.
Use the [Freeze Periods API](../../../api/freeze_periods.md) to set a `freeze_start` and a `freeze_end`, which
are defined as [crontab](https://crontab.guru/) entries.
If the job that's executing is within a freeze period, GitLab CI/CD creates an environment
variable named `$CI_DEPLOY_FREEZE`.
To prevent the deployment job from executing, create a `rules` entry in your
`gitlab-ci.yaml`, for example:
```yaml
deploy_to_production:
stage: deploy
script: deploy_to_prod.sh
rules:
- if: $CI_DEPLOY_FREEZE == null
```
If a project contains multiple freeze periods, all periods apply. If they overlap, the freeze covers the
complete overlapping period.
For more information, see [Deployment safety](../../../ci/environments/deployment_safety.md).
## Release fields
The following fields are available when you create or edit a release.
### Tag name
The release tag name should include the release version. GitLab uses [Semantic Versioning](https://semver.org/)
for our releases, and we recommend you do too. Use `(Major).(Minor).(Patch)`, as detailed in the
[GitLab Policy for Versioning](../../../policy/maintenance.md#versioning).
For example, for GitLab version `10.5.7`:
@ -36,44 +216,31 @@ For example, for GitLab version `10.5.7`:
Any part of the version number can be multiple digits, for example, `13.10.11`.
### Release description
### Release notes description
Every Release has a description. You can add any text you like, but we recommend
including a changelog to describe the content of your release. This will allow
your users to quickly scan the differences between each one you publish.
Every release has a description. You can add any text you like, but we recommend
including a changelog to describe the content of your release. This helps users
quickly scan the differences between each release you publish.
NOTE: **Note:**
[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and
Release descriptions are unrelated. Description supports [Markdown](../../markdown.md).
Release note descriptions are unrelated. Description supports [Markdown](../../markdown.md).
### Release assets
You can currently add the following types of assets to each Release:
You can currently add the following types of assets to each release:
- [Source code](#source-code): state of the repository at the time of the Release
- [Links](#links): to content such as built binaries or documentation
- [Source code](#source-code)
- [Links](#links)
GitLab will support more asset types in the future, including objects such
as pre-built packages, compliance/security evidence, or container images.
#### Source code
GitLab automatically generate `zip`, `tar.gz`, `tar.bz2` and `tar`
archived source code from the given Git tag. These are read-only assets.
#### Links
A link is any URL which can point to whatever you like; documentation, built
binaries, or other related materials. These can be both internal or external
links from your GitLab instance.
The four types of links are "Runbook," "Package," "Image," and "Other."
#### Permanent links to Release assets
#### Permanent links to release assets
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27300) in GitLab 12.9.
The assets associated with a Release are accessible through a permanent URL.
The assets associated with a release are accessible through a permanent URL.
GitLab will always redirect this URL to the actual asset
location, so even if the assets move to a different location, you can continue
to use the same URL. This is defined during [link creation](../../../api/releases/links.md#create-a-link) or [updating](../../../api/releases/links.md#update-a-link).
@ -82,7 +249,7 @@ Each asset has a name, a URL of the *actual* asset location, and optionally, a
`filepath` parameter, which, if you specify it, will create a URL pointing
to the asset for the Release. The format of the URL is:
```html
```plaintext
https://host/namespace/project/releases/:release/downloads/:filepath
```
@ -99,171 +266,45 @@ namespace and `gitlab-runner` project on `gitlab.com`, for example:
This asset has a direct link of:
```html
```plaintext
https://gitlab.com/gitlab-org/gitlab-runner/releases/v11.9.0-rc2/downloads/binaries/gitlab-runner-linux-amd64
```
The physical location of the asset can change at any time and the direct link will remain unchanged.
### Releases associated with milestones
### Source code
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29020) in GitLab 12.5.
> - [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/39467) to edit milestones in the UI in GitLab 13.0.
GitLab automatically generates `zip`, `tar.gz`, `tar.bz2` and `tar`
archived source code from the given Git tag. These are read-only assets.
Releases can optionally be associated with one or more
[project milestones](../milestones/index.md#project-milestones-and-group-milestones)
by including a `milestones` array in your requests to the
[Releases API](../../../api/releases/index.md#create-a-release) or by using the dropdown in the [Edit Release](#editing-a-release) page.
### Links
![Release edit page with milestones dropdown expanded](img/release_milestone_dropdown_v13_0.png)
A link is any URL which can point to whatever you like: documentation, built
binaries, or other related materials. These can be both internal or external
links from your GitLab instance.
Releases display this association with the **Milestone** indicator in the top
section of the Release block on the **Project overview > Releases** page, along
with some statistics about the issues in the milestone(s).
The four types of links are "Runbook," "Package," "Image," and "Other."
![A Release with one associated milestone](img/release_with_milestone_v12_9.png)
Below is an example of milestones with no Releases, one Release, and two
Releases, respectively.
![Milestones with and without Release associations](img/milestone_list_with_releases_v12_5.png)
This relationship is also visible in the **Releases** section of the sidebar
when viewing a specific milestone. Below is an example of a milestone
associated with a large number of Releases.
![Milestone with lots of associated Releases](img/milestone_with_releases_v12_5.png)
## Releases list
Navigate to **Project > Releases** in order to see the list of releases for a given
project.
![Releases list](img/releases_v12_9.png)
### Number of Releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36667) in GitLab 12.8.
The incremental number of Releases is displayed on the project's details page. When clicked,
it takes you to the list of Releases.
![Number of Releases](img/releases_count_v12_8.png "Incremental counter of Releases")
For private projects, the number of Releases is displayed to users with Reporter
[permissions](../../permissions.md#project-members-permissions) or higher. For public projects,
it is displayed to every user regardless of their permission level.
### Upcoming Releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
A Release may be created ahead of time by specifying a future `released_at` date. Until
the `released_at` date and time is reached, an **Upcoming Release** badge will appear next to the
Release tag. Once the `released_at` date and time has passed, the badge is automatically removed.
![An upcoming release](img/upcoming_release_v12_7.png)
## Creating a Release
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32812) in GitLab 12.9, Releases can be created directly through the GitLab Releases UI.
NOTE: **Note:**
Only users with Developer permissions or higher can create Releases.
Read more about [Release permissions](../../../user/permissions.md#project-members-permissions).
To create a new Release through the GitLab UI:
1. Navigate to **Project overview > Releases** and click the **New release** button.
1. On the **New Tag** page, fill out the tag details.
1. Optionally, in the **Release notes** field, enter the Release's description.
If you leave this field empty, only a tag will be created.
If you populate it, both a tag and a Release will be created.
1. Click **Create tag**.
If you created a release, you can view it at **Project overview > Releases**.
You can also create a Release using the [Releases API](../../../api/releases/index.md#create-a-release):
we recommend doing this as one of the last steps in your CI/CD release pipeline.
## Editing a release
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10.
To edit the details of a release, navigate to **Project overview > Releases** and click
the edit button (pencil icon) in the top-right corner of the release you want to modify.
![A release with an edit button](img/release_edit_button_v12_6.png)
This will bring you to the **Edit Release** page, from which you can
change some of the release's details.
![Edit release page](img/edit_release_page_v13_0.png)
Currently, it is only possible to edit the release title, notes, associated milestones, and asset
links. To change other release information, such as its tag, or release date, use the [Releases
API](../../../api/releases/index.md#update-a-release). Editing this information
through the **Edit Release** page is planned for a future version of GitLab.
## Notification for Releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4.
You can be notified by email when a new Release is created for your project.
To subscribe to Release notifications:
1. Navigate to your **Project**'s landing page.
1. Click the bell icon (**Notification setting**).
1. Select **Custom** from the dropdown menu.
![Custom notification - Dropdown menu](img/custom_notifications_dropdown_v12_5.png)
1. Select **New release**.
![Custom notification - New release](img/custom_notifications_new_release_v12_5.png)
## Add release notes to Git tags
You can add release notes to any Git tag using the notes feature. Release notes
behave like any other Markdown form in GitLab so you can write text and
drag and drop files to it. Release notes are stored in GitLab's database.
There are several ways to add release notes:
- In the interface, when you create a new Git tag.
- In the interface, by adding a release note to an existing Git tag.
- Using the [Releases API](../../../api/releases/index.md): (we recommend doing this as one of the last
steps in your CI/CD release pipeline).
To create a new tag, navigate to your project's **Repository > Tags** and
click **New tag**. From there, you can fill the form with all the information
about the release:
![new_tag](img/new_tag_12_5.png "Creation of a new tag.")
You can also edit an existing tag to add release notes:
![tags](img/tags_12_5.png "Addition of note to an existing tag")
## Release Evidence
## Release evidence
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26019) in GitLab 12.6.
Each time a release is created, GitLab takes a snapshot of data that's related to it.
This data is called Release Evidence. It includes linked milestones and issues, and
it is taken at moment the release is created. It provides a chain of custody and can
facilitate processes like external audits, for example.
This data is saved in a JSON file and called *release evidence*. It includes linked milestones
and issues and can facilitate internal processes like external audits.
To access the release evidence, on the Releases page, click the link to the JSON file that's listed
under the **Evidence collection** heading.
You can also [use the API](../../../api/releases/index.md#collect-release-evidence-premium-only) to
generate Release Evidence for an existing release. Because of this, [each release](#releases-list)
can have multiple Release Evidence snapshots. You can view the Release Evidence and
its details on the Release page.
generate release evidence for an existing release. Because of this, each release
can have multiple release evidence snapshots. You can view the release evidence and
its details on the Releases page.
NOTE: **Note:**
When the issue tracker is disabled, release evidence [cannot be downloaded](https://gitlab.com/gitlab-org/gitlab/-/issues/208397).
Release Evidence is stored as a JSON object, so you can compare evidence by using
commonly-available tools.
Here is an example of a Release Evidence object:
Here is an example of a release evidence object:
```json
{
@ -323,28 +364,13 @@ Here is an example of a Release Evidence object:
}
```
### Diabling Release Evidence display **(CORE ONLY)**
This feature comes with the `:release_evidence_collection` feature flag
enabled by default in GitLab self-managed instances. To turn it off,
ask a GitLab administrator with Rails console access to run the following
command:
```ruby
Feature.disable(:release_evidence_collection)
```
NOTE: **Note:**
Please note that Release Evidence's data is collected regardless of this
feature flag, which only enables or disables the display of the data on the
Releases page.
### Collect release evidence **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.10.
Evidence collection can be initiated by using an [API call](../../../api/releases/index.md#collect-release-evidence-premium-only) at any time. Evidence snapshots are visible on
the Release page, along with the timestamp the Evidence was collected.
When a release is created, release evidence is automatically collected. To initiate evidence collection any other time, use an [API call](../../../api/releases/index.md#collect-release-evidence-premium-only). You can collect release evidence multiple times for one release.
Evidence collection snapshots are visible on the Releases page, along with the timestamp the evidence was collected.
### Include report artifacts as release evidence **(ULTIMATE ONLY)**
@ -381,63 +407,43 @@ If you [schedule release evidence collection](#schedule-release-evidence-collect
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23697) in GitLab 12.8.
When the `released_at` date and time is not provided, the date and time of Release
creation is used. The Evidence collection background job is immediately executed.
In the API:
If a future `released_at` is specified, the Release becomes an **Upcoming Release**. In this
case, the Evidence is scheduled to be collected at the `released_at` date and time, via a
background job.
- If you specify a future `released_at` date, the release becomes an **Upcoming Release**
and the evidence is collected on the date of the release. You cannot collect
release evidence before then.
- If you use a past `released_at` date, no evidence is collected.
- If you do not specify a `released_at` date, release evidence is collected on the
date the release is created.
If a past `released_at` is used, no Evidence is collected for the Release.
### Disable release evidence display **(CORE ONLY)**
The `:release_evidence_collection` feature flag is enabled by default in GitLab
self-managed instances. To turn it off, ask a GitLab administrator with Rails console
access to run the following command:
```ruby
Feature.disable(:release_evidence_collection)
```
NOTE: **Note:**
Release evidence is collected regardless of this feature flag,
which only enables or disables the display of the data on the
Releases page.
## GitLab Releaser
> [Introduced](https://gitlab.com/gitlab-org/gitlab-releaser/-/merge_requests/6) in GitLab 12.10.
GitLab Releaser is a CLI tool for managing GitLab Releases from the command line or from
GitLab CI/CD's configuration file, `.gitlab-ci.yml`.
GitLab's CI/CD configuration file, `.gitlab-ci.yml`.
With it, you can create, update, modify, and delete Releases right through the
With it, you can create, update, modify, and delete releases right through the
terminal.
Read the [GitLab Releaser documentation](https://gitlab.com/gitlab-org/gitlab-releaser/-/tree/master/docs/index.md)
for details.
## Set a deploy freeze
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
With a deploy freeze, you can prevent an unintended production release during a
period of time you specify, whether a company event or public holiday. You can
now rely on the enforcement of policies that are typically outside the scope of
GitLab to reduce uncertainty and risk when automating deployments.
Deploy freeze periods are set at the Project level, and may be created and
managed using the [Freeze Periods API](../../../api/freeze_periods.md).
Each Freeze Period has a `freeze_start` and a `freeze_end`, which are defined
as [crontab](https://crontab.guru/) entries. If a project contains multiple
freeze periods, all will apply, and should they overlap, the freeze covers the
complete overlapped period.
During pipeline processing, GitLab CI creates an environment variable named
`$CI_DEPLOY_FREEZE` if the currently executing job is within a
Freeze Period.
To take advantage of this variable, create a `rules` entry in your
`gitlab-ci.yaml` to prevent the deployment job from executing.
For example:
```yaml
deploy_to_production:
stage: deploy
script: deploy_to_prod.sh
rules:
- if: $CI_DEPLOY_FREEZE == null
```
For more information, see [Deployment safety](../../../ci/environments/deployment_safety.md).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -131,6 +131,8 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
mount ::API::Ci::Runner
mount ::API::Ci::Runners
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
@ -195,8 +197,6 @@ module API
mount ::API::Release::Links
mount ::API::RemoteMirrors
mount ::API::Repositories
mount ::API::Runner
mount ::API::Runners
mount ::API::Search
mount ::API::Services
mount ::API::Settings

299
lib/api/ci/runner.rb Normal file
View File

@ -0,0 +1,299 @@
# frozen_string_literal: true
module API
module Ci
class Runner < Grape::API
helpers ::API::Helpers::Runner
resource :runners do
desc 'Registers a new Runner' do
success Entities::RunnerRegistrationDetails
http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: 'Registration token'
optional :description, type: String, desc: %q(Runner's description)
optional :info, type: Hash, desc: %q(Runner's metadata)
optional :active, type: Boolean, desc: 'Should Runner be active'
optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
end
post '/' do
attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
.merge(get_runner_details_from_request)
attributes =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
attributes.merge(runner_type: :instance_type)
elsif project = Project.find_by_runners_token(params[:token])
# Create a specific runner for the project
attributes.merge(runner_type: :project_type, projects: [project])
elsif group = Group.find_by_runners_token(params[:token])
# Create a specific runner for the group
attributes.merge(runner_type: :group_type, groups: [group])
else
forbidden!
end
runner = ::Ci::Runner.create(attributes)
if runner.persisted?
present runner, with: Entities::RunnerRegistrationDetails
else
render_validation_error!(runner)
end
end
desc 'Deletes a registered Runner' do
http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
delete '/' do
authenticate_runner!
runner = ::Ci::Runner.find_by_token(params[:token])
destroy_conditionally!(runner)
end
desc 'Validates authentication credentials' do
http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
post '/verify' do
authenticate_runner!
status 200
end
end
resource :jobs do
before do
Gitlab::ApplicationContext.push(
user: -> { current_job&.user },
project: -> { current_job&.project }
)
end
desc 'Request a job' do
success Entities::JobRequest::Response
http_codes [[201, 'Job was scheduled'],
[204, 'No job for Runner'],
[403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token)
optional :info, type: Hash, desc: %q(Runner's metadata) do
optional :name, type: String, desc: %q(Runner's name)
optional :version, type: String, desc: %q(Runner's version)
optional :revision, type: String, desc: %q(Runner's revision)
optional :platform, type: String, desc: %q(Runner's platform)
optional :architecture, type: String, desc: %q(Runner's architecture)
optional :executor, type: String, desc: %q(Runner's executor)
optional :features, type: Hash, desc: %q(Runner's features)
end
optional :session, type: Hash, desc: %q(Runner's session data) do
optional :url, type: String, desc: %q(Session's url)
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
end
post '/request' do
authenticate_runner!
unless current_runner.active?
header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
break no_content!
end
runner_params = declared_params(include_missing: false)
if current_runner.runner_queue_value_latest?(runner_params[:last_update])
header 'X-GitLab-Last-Update', runner_params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached)
break no_content!
end
new_update = current_runner.ensure_runner_queue_value
result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
if result.valid?
if result.build
Gitlab::Metrics.add_event(:build_found)
present ::Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
else
Gitlab::Metrics.add_event(:build_not_found)
header 'X-GitLab-Last-Update', new_update
no_content!
end
else
# We received build that is invalid due to concurrency conflict
Gitlab::Metrics.add_event(:build_invalid)
conflict!
end
end
desc 'Updates a job' do
http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runners's authentication token)
requires :id, type: Integer, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed)
optional :failure_reason, type: String, desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
job.trace.set(params[:trace]) if params[:trace]
Gitlab::Metrics.add_event(:update_build)
case params[:state].to_s
when 'running'
job.touch if job.needs_touch?
when 'success'
job.success!
when 'failed'
job.drop!(params[:failure_reason] || :unknown_failure)
end
end
desc 'Appends a patch to the job trace' do
http_codes [[202, 'Trace was patched'],
[400, 'Missing Content-Range header'],
[403, 'Forbidden'],
[416, 'Range not satisfiable']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
end
patch '/:id/trace' do
job = authenticate_job!
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
content_range = request.headers['Content-Range']
content_range = content_range.split('-')
# TODO:
# it seems that `Content-Range` as formatted by runner is wrong,
# the `byte_end` should point to final byte, but it points byte+1
# that means that we have to calculate end of body,
# as we cannot use `content_length[1]`
# Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
body_data = request.body.read
body_start = content_range[0].to_i
body_end = body_start + body_data.bytesize
stream_size = job.trace.append(body_data, body_start)
unless stream_size == body_end
break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
end
status 202
header 'Job-Status', job.status
header 'Range', "0-#{stream_size}"
header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
end
desc 'Authorize artifacts uploading for job' do
http_codes [[200, 'Upload allowed'],
[403, 'Forbidden'],
[405, 'Artifacts support not enabled'],
[413, 'File too large']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
optional :filesize, type: Integer, desc: %q(Artifacts filesize)
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: ::Ci::JobArtifact.file_types.keys
end
post '/:id/artifacts/authorize' do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
Gitlab::Workhorse.verify_api_request!(headers)
job = authenticate_job!
service = ::Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job))
forbidden! if service.forbidden?
file_too_large! if service.too_large?
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
service.headers
end
desc 'Upload artifacts for job' do
success Entities::JobRequest::Response
http_codes [[201, 'Artifact uploaded'],
[400, 'Bad request'],
[403, 'Forbidden'],
[405, 'Artifacts support not enabled'],
[413, 'File too large']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
optional :token, type: String, desc: %q(Job's authentication token)
optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: ::Ci::JobArtifact.file_types.keys
optional :artifact_format, type: String, desc: %q(The format of artifact),
default: 'zip', values: ::Ci::JobArtifact.file_formats.keys
optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
end
post '/:id/artifacts' do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
job = authenticate_job!
artifacts = params[:file]
metadata = params[:metadata]
file_too_large! unless artifacts.size < max_artifacts_size(job)
result = ::Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata)
if result[:status] == :success
status :created
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Download the artifacts file for job' do
http_codes [[200, 'Upload allowed'],
[403, 'Forbidden'],
[404, 'Artifact not found']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
end
get '/:id/artifacts' do
job = authenticate_job!(require_running: false)
present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
end
end
end
end
end

289
lib/api/ci/runners.rb Normal file
View File

@ -0,0 +1,289 @@
# frozen_string_literal: true
module API
module Ci
class Runners < Grape::API
include PaginationParams
before { authenticate! }
resource :runners do
desc 'Get runners available for user' do
success Entities::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get do
runners = current_user.ci_owned_runners
runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
desc 'Get all runners - shared and specific' do
success Entities::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get 'all' do
authenticated_as_admin!
runners = ::Ci::Runner.all
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
desc "Get runner's details" do
success Entities::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
get ':id' do
runner = get_runner(params[:id])
authenticate_show_runner!(runner)
present runner, with: Entities::RunnerDetails, current_user: current_user
end
desc "Update runner's details" do
success Entities::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :description, type: String, desc: 'The description of the runner'
optional :active, type: Boolean, desc: 'The state of a runner'
optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
end
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
update_service = ::Ci::UpdateRunnerService.new(runner)
if update_service.update(declared_params(include_missing: false))
present runner, with: Entities::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
end
end
desc 'Remove a runner' do
success Entities::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
delete ':id' do
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
destroy_conditionally!(runner)
end
desc 'List jobs running on a runner' do
success Entities::JobBasicWithProject
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute
present paginate(jobs), with: Entities::JobBasicWithProject
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_project }
desc 'Get runners available for project' do
success Entities::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = ::Ci::Runner.owned_or_instance_wide(user_project.id)
# scope is deprecated (for project runners), however api documentation still supports it.
# Not including them in `apply_filter` method as it's not supported for group runners
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Runner
end
desc 'Enable a runner for a project' do
success Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
post ':id/runners' do
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
if runner.assign_to(user_project)
present runner, with: Entities::Runner
else
render_validation_error!(runner)
end
end
desc "Disable project's runner" do
success Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
destroy_conditionally!(runner_project)
end
# rubocop: enable CodeReuse/ActiveRecord
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_group }
desc 'Get runners available for group' do
success Entities::Runner
end
params do
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = ::Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Runner
end
end
helpers do
def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
return runners unless scope.present?
unless allowed_scopes.include?(scope)
render_api_error!('Scope contains invalid value', 400)
end
# Support deprecated scopes
if runners.respond_to?("deprecated_#{scope}")
scope = "deprecated_#{scope}"
end
runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
end
def apply_filter(runners, params)
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
runners
end
def get_runner(id)
runner = ::Ci::Runner.find(id)
not_found!('Runner') unless runner
runner
end
def authenticate_show_runner!(runner)
return if runner.instance_type? || current_user.admin?
forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
end
def authenticate_update_runner!(runner)
return if current_user.admin?
forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
end
def authenticate_delete_runner!(runner)
return if current_user.admin?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
end
def authenticate_enable_runner!(runner)
forbidden!("Runner is a group runner") if runner.group_type?
return if current_user.admin?
forbidden!("Runner is locked") if runner.locked?
forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
end
def authenticate_list_runners_jobs!(runner)
return if current_user.admin?
forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
end
end
end
end
end

View File

@ -60,7 +60,7 @@ module API
not_found! 'Commit' unless commit
# Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline)
# Since the CommitStatus is attached to ::Ci::Pipeline (in the future Pipeline)
# We need to always have the pipeline object
# To have a valid pipeline object that can be attached to specific MR
# Other CI service needs to send `ref`

View File

@ -48,7 +48,7 @@ module API
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@ -70,7 +70,7 @@ module API
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do

View File

@ -60,7 +60,7 @@ module API
def current_job
strong_memoize(:current_job) do
Ci::Build.find_by_id(params[:id])
::Ci::Build.find_by_id(params[:id])
end
end

View File

@ -160,7 +160,7 @@ module API
authorize!(:update_build, build)
break forbidden!('Job is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user)
build = ::Ci::Build.retry(build, current_user)
present build, with: Entities::Job
end

View File

@ -22,7 +22,7 @@ module API
get ':id/pipeline_schedules' do
authorize! :read_pipeline_schedule, user_project
schedules = Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
schedules = ::Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
.preload([:owner, :last_pipeline])
present paginate(schedules), with: Entities::PipelineSchedule
end
@ -51,7 +51,7 @@ module API
post ':id/pipeline_schedules' do
authorize! :create_pipeline_schedule, user_project
pipeline_schedule = Ci::CreatePipelineScheduleService
pipeline_schedule = ::Ci::CreatePipelineScheduleService
.new(user_project, current_user, declared_params(include_missing: false))
.execute
@ -137,7 +137,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
authorize! :update_pipeline_schedule, pipeline_schedule
@ -158,7 +158,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
requires :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
end
put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
authorize! :update_pipeline_schedule, pipeline_schedule

View File

@ -18,7 +18,7 @@ module API
use :pagination
optional :scope, type: String, values: %w[running pending finished branches tags],
desc: 'The scope of pipelines'
optional :status, type: String, values: Ci::HasStatus::AVAILABLE_STATUSES,
optional :status, type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES,
desc: 'The status of pipelines'
optional :ref, type: String, desc: 'The ref of pipelines'
optional :sha, type: String, desc: 'The sha of pipelines'
@ -27,7 +27,7 @@ module API
optional :username, type: String, desc: 'The username of the user who triggered pipelines'
optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :order_by, type: String, values: Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
desc: 'Order pipelines'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Sort pipelines'
@ -36,7 +36,7 @@ module API
authorize! :read_pipeline, user_project
authorize! :read_build, user_project
pipelines = Ci::PipelinesFinder.new(user_project, current_user, params).execute
pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute
present paginate(pipelines), with: Entities::PipelineBasic
end
@ -57,9 +57,9 @@ module API
.merge(variables_attributes: params[:variables])
.except(:variables)
new_pipeline = Ci::CreatePipelineService.new(user_project,
current_user,
pipeline_params)
new_pipeline = ::Ci::CreatePipelineService.new(user_project,
current_user,
pipeline_params)
.execute(:api, ignore_skip_ci: true, save_on_errors: false)
if new_pipeline.persisted?

View File

@ -1,297 +0,0 @@
# frozen_string_literal: true
module API
class Runner < Grape::API
helpers ::API::Helpers::Runner
resource :runners do
desc 'Registers a new Runner' do
success Entities::RunnerRegistrationDetails
http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: 'Registration token'
optional :description, type: String, desc: %q(Runner's description)
optional :info, type: Hash, desc: %q(Runner's metadata)
optional :active, type: Boolean, desc: 'Should Runner be active'
optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
end
post '/' do
attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
.merge(get_runner_details_from_request)
attributes =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
attributes.merge(runner_type: :instance_type)
elsif project = Project.find_by_runners_token(params[:token])
# Create a specific runner for the project
attributes.merge(runner_type: :project_type, projects: [project])
elsif group = Group.find_by_runners_token(params[:token])
# Create a specific runner for the group
attributes.merge(runner_type: :group_type, groups: [group])
else
forbidden!
end
runner = Ci::Runner.create(attributes)
if runner.persisted?
present runner, with: Entities::RunnerRegistrationDetails
else
render_validation_error!(runner)
end
end
desc 'Deletes a registered Runner' do
http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
delete '/' do
authenticate_runner!
runner = Ci::Runner.find_by_token(params[:token])
destroy_conditionally!(runner)
end
desc 'Validates authentication credentials' do
http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
post '/verify' do
authenticate_runner!
status 200
end
end
resource :jobs do
before do
Gitlab::ApplicationContext.push(
user: -> { current_job&.user },
project: -> { current_job&.project }
)
end
desc 'Request a job' do
success Entities::JobRequest::Response
http_codes [[201, 'Job was scheduled'],
[204, 'No job for Runner'],
[403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token)
optional :info, type: Hash, desc: %q(Runner's metadata) do
optional :name, type: String, desc: %q(Runner's name)
optional :version, type: String, desc: %q(Runner's version)
optional :revision, type: String, desc: %q(Runner's revision)
optional :platform, type: String, desc: %q(Runner's platform)
optional :architecture, type: String, desc: %q(Runner's architecture)
optional :executor, type: String, desc: %q(Runner's executor)
optional :features, type: Hash, desc: %q(Runner's features)
end
optional :session, type: Hash, desc: %q(Runner's session data) do
optional :url, type: String, desc: %q(Session's url)
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
end
post '/request' do
authenticate_runner!
unless current_runner.active?
header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
break no_content!
end
runner_params = declared_params(include_missing: false)
if current_runner.runner_queue_value_latest?(runner_params[:last_update])
header 'X-GitLab-Last-Update', runner_params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached)
break no_content!
end
new_update = current_runner.ensure_runner_queue_value
result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
if result.valid?
if result.build
Gitlab::Metrics.add_event(:build_found)
present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
else
Gitlab::Metrics.add_event(:build_not_found)
header 'X-GitLab-Last-Update', new_update
no_content!
end
else
# We received build that is invalid due to concurrency conflict
Gitlab::Metrics.add_event(:build_invalid)
conflict!
end
end
desc 'Updates a job' do
http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runners's authentication token)
requires :id, type: Integer, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed)
optional :failure_reason, type: String, desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
job.trace.set(params[:trace]) if params[:trace]
Gitlab::Metrics.add_event(:update_build)
case params[:state].to_s
when 'running'
job.touch if job.needs_touch?
when 'success'
job.success!
when 'failed'
job.drop!(params[:failure_reason] || :unknown_failure)
end
end
desc 'Appends a patch to the job trace' do
http_codes [[202, 'Trace was patched'],
[400, 'Missing Content-Range header'],
[403, 'Forbidden'],
[416, 'Range not satisfiable']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
end
patch '/:id/trace' do
job = authenticate_job!
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
content_range = request.headers['Content-Range']
content_range = content_range.split('-')
# TODO:
# it seems that `Content-Range` as formatted by runner is wrong,
# the `byte_end` should point to final byte, but it points byte+1
# that means that we have to calculate end of body,
# as we cannot use `content_length[1]`
# Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
body_data = request.body.read
body_start = content_range[0].to_i
body_end = body_start + body_data.bytesize
stream_size = job.trace.append(body_data, body_start)
unless stream_size == body_end
break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
end
status 202
header 'Job-Status', job.status
header 'Range', "0-#{stream_size}"
header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
end
desc 'Authorize artifacts uploading for job' do
http_codes [[200, 'Upload allowed'],
[403, 'Forbidden'],
[405, 'Artifacts support not enabled'],
[413, 'File too large']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
optional :filesize, type: Integer, desc: %q(Artifacts filesize)
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: Ci::JobArtifact.file_types.keys
end
post '/:id/artifacts/authorize' do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
Gitlab::Workhorse.verify_api_request!(headers)
job = authenticate_job!
service = Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job))
forbidden! if service.forbidden?
file_too_large! if service.too_large?
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
service.headers
end
desc 'Upload artifacts for job' do
success Entities::JobRequest::Response
http_codes [[201, 'Artifact uploaded'],
[400, 'Bad request'],
[403, 'Forbidden'],
[405, 'Artifacts support not enabled'],
[413, 'File too large']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
optional :token, type: String, desc: %q(Job's authentication token)
optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: Ci::JobArtifact.file_types.keys
optional :artifact_format, type: String, desc: %q(The format of artifact),
default: 'zip', values: Ci::JobArtifact.file_formats.keys
optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
end
post '/:id/artifacts' do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
job = authenticate_job!
artifacts = params[:file]
metadata = params[:metadata]
file_too_large! unless artifacts.size < max_artifacts_size(job)
result = Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata)
if result[:status] == :success
status :created
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Download the artifacts file for job' do
http_codes [[200, 'Upload allowed'],
[403, 'Forbidden'],
[404, 'Artifact not found']]
end
params do
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
end
get '/:id/artifacts' do
job = authenticate_job!(require_running: false)
present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
end
end
end
end

View File

@ -1,287 +0,0 @@
# frozen_string_literal: true
module API
class Runners < Grape::API
include PaginationParams
before { authenticate! }
resource :runners do
desc 'Get runners available for user' do
success Entities::Runner
end
params do
optional :scope, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get do
runners = current_user.ci_owned_runners
runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
desc 'Get all runners - shared and specific' do
success Entities::Runner
end
params do
optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get 'all' do
authenticated_as_admin!
runners = Ci::Runner.all
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
desc "Get runner's details" do
success Entities::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
get ':id' do
runner = get_runner(params[:id])
authenticate_show_runner!(runner)
present runner, with: Entities::RunnerDetails, current_user: current_user
end
desc "Update runner's details" do
success Entities::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :description, type: String, desc: 'The description of the runner'
optional :active, type: Boolean, desc: 'The state of a runner'
optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
end
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
update_service = Ci::UpdateRunnerService.new(runner)
if update_service.update(declared_params(include_missing: false))
present runner, with: Entities::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
end
end
desc 'Remove a runner' do
success Entities::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
delete ':id' do
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
destroy_conditionally!(runner)
end
desc 'List jobs running on a runner' do
success Entities::JobBasicWithProject
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
optional :order_by, type: String, desc: 'Order by `id` or not', values: Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
jobs = Ci::RunnerJobsFinder.new(runner, params).execute
present paginate(jobs), with: Entities::JobBasicWithProject
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_project }
desc 'Get runners available for project' do
success Entities::Runner
end
params do
optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = Ci::Runner.owned_or_instance_wide(user_project.id)
# scope is deprecated (for project runners), however api documentation still supports it.
# Not including them in `apply_filter` method as it's not supported for group runners
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Runner
end
desc 'Enable a runner for a project' do
success Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
post ':id/runners' do
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
if runner.assign_to(user_project)
present runner, with: Entities::Runner
else
render_validation_error!(runner)
end
end
desc "Disable project's runner" do
success Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
destroy_conditionally!(runner_project)
end
# rubocop: enable CodeReuse/ActiveRecord
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_group }
desc 'Get runners available for group' do
success Entities::Runner
end
params do
optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Runner
end
end
helpers do
def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
return runners unless scope.present?
unless allowed_scopes.include?(scope)
render_api_error!('Scope contains invalid value', 400)
end
# Support deprecated scopes
if runners.respond_to?("deprecated_#{scope}")
scope = "deprecated_#{scope}"
end
runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
end
def apply_filter(runners, params)
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
runners
end
def get_runner(id)
runner = Ci::Runner.find(id)
not_found!('Runner') unless runner
runner
end
def authenticate_show_runner!(runner)
return if runner.instance_type? || current_user.admin?
forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
end
def authenticate_update_runner!(runner)
return if current_user.admin?
forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
end
def authenticate_delete_runner!(runner)
return if current_user.admin?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
end
def authenticate_enable_runner!(runner)
forbidden!("Runner is a group runner") if runner.group_type?
return if current_user.admin?
forbidden!("Runner is locked") if runner.locked?
forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
end
def authenticate_list_runners_jobs!(runner)
return if current_user.admin?
forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
end
end
end
end

View File

@ -32,7 +32,7 @@ module API
project = find_project(params[:id])
not_found! unless project
result = Ci::PipelineTriggerService.new(project, nil, params).execute
result = ::Ci::PipelineTriggerService.new(project, nil, params).execute
not_found! unless result
if result[:http_status]

View File

@ -56,7 +56,7 @@ module API
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
post ':id/variables' do
@ -80,7 +80,7 @@ module API
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -558,6 +558,18 @@ module Gitlab
end
end
def issue_minimum_id
strong_memoize(:issue_minimum_id) do
::Issue.minimum(:id)
end
end
def issue_maximum_id
strong_memoize(:issue_maximum_id) do
::Issue.maximum(:id)
end
end
def clear_memoized
clear_memoization(:user_minimum_id)
clear_memoization(:user_maximum_id)

View File

@ -165,10 +165,13 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it 'creates a partition spanning over each month in the range given' do
migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
expect_range_partition_of("#{partitioned_table}_000000", partitioned_table, 'MINVALUE', "'2019-12-01 00:00:00'")
expect_range_partition_of("#{partitioned_table}_201912", partitioned_table, "'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'")
expect_range_partition_of("#{partitioned_table}_202001", partitioned_table, "'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'")
expect_range_partition_of("#{partitioned_table}_202002", partitioned_table, "'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'")
expect_range_partitions_for(partitioned_table, {
'000000' => ['MINVALUE', "'2019-12-01 00:00:00'"],
'201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
'202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
'202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
'202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
})
end
end

View File

@ -2974,19 +2974,6 @@ RSpec.describe Ci::Build do
it { is_expected.to include(deployment_variable) }
end
context 'when build has a freeze period' do
let(:freeze_variable) { { key: 'CI_DEPLOY_FREEZE', value: 'true', masked: false, public: true } }
before do
expect_next_instance_of(Ci::FreezePeriodStatus) do |freeze_period|
expect(freeze_period).to receive(:execute)
.and_return(true)
end
end
it { is_expected.to include(freeze_variable) }
end
context 'when project has default CI config path' do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
@ -13,7 +13,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
@ -38,7 +38,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
it 'creates runner with default values' do
post api('/runners'), params: { token: registration_token }
runner = Ci::Runner.first
runner = ::Ci::Runner.first
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(runner.id)
@ -57,7 +57,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(project.runners.size).to eq(1)
runner = Ci::Runner.first
runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
@ -72,7 +72,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(group.runners.reload.size).to eq(1)
runner = Ci::Runner.first
runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
@ -88,7 +88,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.description).to eq('server.hostname')
expect(::Ci::Runner.first.description).to eq('server.hostname')
end
end
@ -100,7 +100,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
@ -114,8 +114,8 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.run_untagged).to be false
expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
expect(::Ci::Runner.first.run_untagged).to be false
expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
end
end
@ -141,7 +141,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.locked).to be true
expect(::Ci::Runner.first.locked).to be true
end
end
@ -154,7 +154,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.active).to be true
expect(::Ci::Runner.first.active).to be true
end
end
@ -166,7 +166,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.active).to be false
expect(::Ci::Runner.first.active).to be false
end
end
end
@ -180,7 +180,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.ref_protected?).to be true
expect(::Ci::Runner.first.ref_protected?).to be true
end
end
@ -192,7 +192,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.ref_protected?).to be false
expect(::Ci::Runner.first.ref_protected?).to be false
end
end
end
@ -205,7 +205,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.maximum_timeout).to eq(9000)
expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
end
context 'when maximum job timeout is empty' do
@ -216,7 +216,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.maximum_timeout).to be_nil
expect(::Ci::Runner.first.maximum_timeout).to be_nil
end
end
end
@ -232,7 +232,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
end
end
end
@ -243,7 +243,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status(:created)
expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
end
end
@ -271,7 +271,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
delete api('/runners'), params: { token: runner.token }
expect(response).to have_gitlab_http_status(:no_content)
expect(Ci::Runner.count).to eq(0)
expect(::Ci::Runner.count).to eq(0)
end
it_behaves_like '412 response' do
@ -537,7 +537,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
end
it 'creates persistent ref' do
expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref)
expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref)
.with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
request_job info: { platform: :darwin }
@ -749,7 +749,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when concurrently updating a job' do
before do
expect_any_instance_of(Ci::Build).to receive(:run!)
expect_any_instance_of(::Ci::Build).to receive(:run!)
.and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
end
@ -890,7 +890,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
before do
project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
shared_examples 'expected variables behavior' do
@ -1099,7 +1099,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:project) { create(:project, :repository) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
let(:pipeline) { service[:pipeline] }
let(:build) { pipeline.builds.first }
let(:job) { {} }
@ -2258,7 +2258,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
FileUtils.remove_entry(new_tmpdir)
end
it' "fails to post artifacts for outside of tmp path"' do
it 'fails to post artifacts for outside of tmp path' do
upload_artifacts(file_upload, headers_with_token)
expect(response).to have_gitlab_http_status(:bad_request)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Runners do
RSpec.describe API::Ci::Runners do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
@ -266,7 +266,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.project_type.count }.by(-1)
end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
@ -493,7 +493,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{shared_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.instance_type.count }.by(-1)
end.to change { ::Ci::Runner.instance_type.count }.by(-1)
end
it_behaves_like '412 response' do
@ -507,7 +507,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.project_type.count }.by(-1)
end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
@ -542,7 +542,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{project_runner.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.project_type.count }.by(-1)
end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
it 'does not delete group runner with guest access' do
@ -574,7 +574,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{group_runner_a.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.group_type.count }.by(-1)
end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it 'deletes inherited group runner with owner access' do
@ -582,7 +582,7 @@ RSpec.describe API::Runners do
delete api("/runners/#{group_runner_b.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { Ci::Runner.group_type.count }.by(-1)
end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it_behaves_like '412 response' do
@ -968,7 +968,7 @@ RSpec.describe API::Runners do
end
it 'does not enable locked runner' do
project_runner2.update(locked: true)
project_runner2.update!(locked: true)
expect do
post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }

View File

@ -2205,6 +2205,83 @@ RSpec.describe Ci::CreatePipelineService do
expect(find_job('job-7').when).to eq('on_failure')
end
end
context 'with deploy freeze period `if:` clause' do
# '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') }
context 'with 2 jobs' do
let(:config) do
<<-EOY
stages:
- test
- deploy
test-job:
script:
- echo 'running TEST stage'
deploy-job:
stage: deploy
script:
- echo 'running DEPLOY stage'
rules:
- if: $CI_DEPLOY_FREEZE == null
EOY
end
context 'when outside freeze period' do
Timecop.freeze(2020, 4, 10, 22, 59) do
it 'creates two jobs' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('test-job', 'deploy-job')
end
end
end
context 'when inside freeze period' do
it 'creates one job' do
Timecop.freeze(2020, 4, 10, 23, 1) do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('test-job')
end
end
end
end
context 'with 1 job' do
let(:config) do
<<-EOY
stages:
- deploy
deploy-job:
stage: deploy
script:
- echo 'running DEPLOY stage'
rules:
- if: $CI_DEPLOY_FREEZE == null
EOY
end
context 'when outside freeze period' do
Timecop.freeze(2020, 4, 10, 22, 59) do
it 'creates two jobs' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('deploy-job')
end
end
end
context 'when inside freeze period' do
it 'does not create the pipeline' do
Timecop.freeze(2020, 4, 10, 23, 1) do
expect(pipeline).not_to be_persisted
end
end
end
end
end
end
end

View File

@ -16,6 +16,21 @@ module PartitioningHelpers
expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})")
end
def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
partitions = find_partitions(table_name, schema: schema)
expect(partitions.size).to eq(count)
end
def expect_range_partitions_for(table_name, partitions)
partitions.each do |suffix, (min_value, max_value)|
partition_name = "#{table_name}_#{suffix}"
expect_range_partition_of(partition_name, table_name, min_value, max_value)
end
expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
end
private
def find_partitioned_columns(table)
@ -54,4 +69,18 @@ module PartitioningHelpers
and pg_class.relispartition
SQL
end
def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
connection.select_rows(<<~SQL)
select
pg_class.relname
from pg_class
inner join pg_inherits i on pg_class.oid = inhrelid
inner join pg_class parent_class on parent_class.oid = inhparent
inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
where pg_namespace.nspname = '#{schema}'
and parent_class.relname = '#{partition}'
and pg_class.relispartition
SQL
end
end