Add latest changes from gitlab-org/gitlab@master
|
@ -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 = '';
|
||||
}
|
||||
|
|
|
@ -162,10 +162,6 @@ $mr-widget-min-height: 69px;
|
|||
.btn {
|
||||
font-size: $gl-font-size;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&.dropdown-toggle {
|
||||
.fa {
|
||||
color: inherit;
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Properly set CI_DEPLOY_FREEZE variable in pipelines
|
||||
merge_request: 35226
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix styling bug for disabled merge button
|
||||
merge_request: 35365
|
||||
author:
|
||||
type: fixed
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 279 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 31 KiB |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 } }
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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 }
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|