Commit d41db350 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents e5627a99 d85e04e4
......@@ -28,4 +28,14 @@ module RunnersHelper
display_name + id
end
end
# Due to inability of performing sorting of runners by cached "contacted_at" values we have to show uncached values if sorting by "contacted_asc" is requested.
# Please refer to the following issue for more details: https://gitlab.com/gitlab-org/gitlab-ce/issues/55920
def runner_contacted_at(runner)
if params[:sort] == 'contacted_asc'
runner.uncached_contacted_at
else
runner.contacted_at
end
end
end
......@@ -256,6 +256,10 @@ module Ci
end
end
def uncached_contacted_at
read_attribute(:contacted_at)
end
private
def cleanup_runner_queue
......
......@@ -56,8 +56,9 @@
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Last contact')
.table-mobile-content
- if runner.contacted_at
= time_ago_with_tooltip runner.contacted_at
- contacted_at = runner_contacted_at(runner)
- if contacted_at
= time_ago_with_tooltip contacted_at
- else
= _('Never')
......
---
title: Adds API documentation for releases
merge_request: 23901
author:
type: added
---
title: Allow to include templates in gitlab-ci.yml
merge_request: 23495
author:
type: added
# Releases API
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
> - Using this API you can manipulate GitLab's [Release](../user/project/releases.md) entries.
## List Releases
Paginated list of Releases, sorted by `created_at`.
```
GET /projects/:id/releases
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
Example request:
```sh
curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases"
```
Example response:
```json
[
{
"tag_name":"v0.2",
"description":"## CHANGELOG\r\n\r\n- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2740\r\n- Prevent private snippets from being embeddable.\r\n- Add subresources removal to member destroy service.",
"name":"Awesome app v0.2 beta",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eEscape label and milestone titles to prevent XSS in GFM autocomplete. !2740\u003c/li\u003e\n\u003cli\u003ePrevent private snippets from being embeddable.\u003c/li\u003e\n\u003cli\u003eAdd subresources removal to member destroy service.\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:56:19.539Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"079e90101242458910cccd35eab0e211dfc359c0",
"short_id":"079e9010",
"title":"Update README.md",
"created_at":"2019-01-03T01:55:38.000Z",
"parent_ids":[
"f8d3d94cbd347e924aa7b715845e439d00e80ca4"
],
"message":"Update README.md",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:55:38.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:55:38.000Z"
},
"assets":{
"count":6,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar"
}
],
"links":[
{
"id":2,
"name":"awesome-v0.2.msi",
"url":"http://192.168.10.15:3000/msi",
"external":true
},
{
"id":1,
"name":"awesome-v0.2.dmg",
"url":"http://192.168.10.15:3000",
"external":true
}
]
}
},
{
"tag_name":"v0.1",
"description":"## CHANGELOG\r\n\r\n-Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
"short_id":"f8d3d94c",
"title":"Initial commit",
"created_at":"2019-01-03T01:53:28.000Z",
"parent_ids":[
],
"message":"Initial commit",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:53:28.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
"assets":{
"count":4,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
]
}
}
]
```
## Get a Release by a tag name
Get a Release for the given tag.
```
GET /projects/:id/releases/:tag_name
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
```sh
curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
```
Example response:
```json
{
"tag_name":"v0.1",
"description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
"short_id":"f8d3d94c",
"title":"Initial commit",
"created_at":"2019-01-03T01:53:28.000Z",
"parent_ids":[
],
"message":"Initial commit",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:53:28.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
"assets":{
"count":4,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
]
}
}
```
## Create a release
Create a Release. You need push access to the repository to create a Release.
```
POST /projects/:id/releases
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | yes | The release name. |
| `tag_name` | string | yes | The tag where the release will be created from. |
| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). |
| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
| `assets:links`| array of hash | no | An array of assets links. |
| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
| `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. |
Example request:
```sh
curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
--data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \
--request POST http://localhost:3000/api/v4/projects/24/releases
```
Example response:
```json
{
"tag_name":"v0.3",
"description":"Super nice release",
"name":"New release",
"description_html":"\u003cp dir=\"auto\"\u003eSuper nice release\u003c/p\u003e",
"created_at":"2019-01-03T02:22:45.118Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"079e90101242458910cccd35eab0e211dfc359c0",
"short_id":"079e9010",
"title":"Update README.md",
"created_at":"2019-01-03T01:55:38.000Z",
"parent_ids":[
"f8d3d94cbd347e924aa7b715845e439d00e80ca4"
],
"message":"Update README.md",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:55:38.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:55:38.000Z"
},
"assets":{
"count":5,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar"
}
],
"links":[
{
"id":3,
"name":"hoge",
"url":"https://google.com",
"external":true
}
]
}
}
```
## Update a release
Update a Release.
```
PUT /projects/:id/releases/:tag_name
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `name` | string | no | The release name. |
| `tag_name` | string | no | The tag where the release will be created from. |
| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). |
Example request:
```sh
curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
```
Example response:
```json
{
"tag_name":"v0.1",
"description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
"short_id":"f8d3d94c",
"title":"Initial commit",
"created_at":"2019-01-03T01:53:28.000Z",
"parent_ids":[
],
"message":"Initial commit",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:53:28.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
"assets":{
"count":4,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
]
}
}
```
## Delete a Release
Delete a Release. Deleting a Release will not delete the associated tag.
```
DELETE /projects/:id/releases/:tag_name
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
```sh
curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
```
Example response:
```json
{
"tag_name":"v0.1",
"description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url":"http://localhost:3000/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
"short_id":"f8d3d94c",
"title":"Initial commit",
"created_at":"2019-01-03T01:53:28.000Z",
"parent_ids":[
],
"message":"Initial commit",
"author_name":"Administrator",
"author_email":"admin@example.com",
"authored_date":"2019-01-03T01:53:28.000Z",
"committer_name":"Administrator",
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
"assets":{
"count":4,
"sources":[
{
"format":"zip",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
"url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
]
}
}
```
......@@ -392,8 +392,8 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
If a job does not have neither `only` nor `except` rule,
`only: ['branches', 'tags']` is set by default.
If a job does not have an `only` rule, `only: ['branches', 'tags']` is set by
default. If it doesn't have an `except` rule, it is empty.
For example,
......@@ -1649,6 +1649,7 @@ test:
> Behaviour expanded in GitLab 10.8 to allow more flexible overriding.
> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603)
to GitLab Core in 11.4
> In GitLab 11.7, support for including [GitLab-supplied templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates) directly [was added](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445).
Using the `include` keyword, you can allow the inclusion of external YAML files.
......@@ -1688,6 +1689,13 @@ relative URLs. The following examples are both valid:
include: '/templates/.after-script-template.yml'
```
```yaml
# Single string
include:
file: '/templates/.after-script-template.yml'
```
```yaml
# Array
......@@ -1696,9 +1704,27 @@ include:
- '/templates/.after-script-template.yml'
```
```yaml
# Array mixed syntax
include:
- 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- '/templates/.after-script-template.yml'
- template: Auto-DevOps.gitlab-ci.yml
```
```yaml
# Array
include:
- remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- local: '/templates/.after-script-template.yml'
- template: Auto-DevOps.gitlab-ci.yml
```
---
`include` supports two types of files:
`include` supports three types of files:
- **local** to the same repository, referenced by using full paths in the same
repository, with `/` being the root directory. For example:
......@@ -1708,6 +1734,14 @@ include:
include: '/templates/.gitlab-ci-template.yml'
```
Or using:
```yaml
# Within the repository
include:
local: '/templates/.gitlab-ci-template.yml'
```
NOTE: **Note:**
You can only use files that are currently tracked by Git on the same branch
your configuration file is. In other words, when using a **local file**, make
......@@ -1720,9 +1754,18 @@ include:
using the full URL. For example:
```yaml
# File sourced from outside repository
include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
Or using:
```yaml
# File sourced from outside repository
include:
remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
NOTE: **Note:**
The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
......@@ -1731,6 +1774,17 @@ include:
you may need to enable the **Allow requests to the local network from hooks and services** checkbox
located in the **Settings > Network > Outbound requests** section within the **Admin area**.
- **template** included with GitLab. For example:
```yaml
# File sourced from GitLab's template collection
include:
template: Auto-DevOps.gitlab-ci.yml
```
NOTE: **Note:**
Templates included this way are sourced from [lib/gitlab/ci/templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
---
......
doc/user/project/img/releases.png

42.6 KB | W: | H:

doc/user/project/img/releases.png

123 KB | W: | H:

doc/user/project/img/releases.png
doc/user/project/img/releases.png
doc/user/project/img/releases.png
doc/user/project/img/releases.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -2,11 +2,58 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
Releases mark specific points in a project's development history, communicate
information about the type of change, and deliver on prepared, often compiled,
versions of the software to be reused elsewhere. Currently, releases can only be
created through the API.
It's 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.
Navigate to **Project > Releases** in order to see the list of releases of a project.
GitLab's **Releases** are a way to track deliverables in your project. Consider them
a snapshot in time of the source, build output, and other metadata or artifacts
associated with a released version of your code.
At the moment, you can create Release entries via the [Releases API](../../api/releases.md);
we recommend doing this as one of the last steps in your CI/CD release pipeline.
## Getting started with Releases
Start by giving a [description](#release-description) to the Release and
including its [assets](#release-assets), as follows.
### Release 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.
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 assets
You can currently add the following types of assets to each Release:
- [Source code](#source-code): state of the repo at the time of the Release
- [Links](#links): to content such as built binaries or documentation
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.
## Releases list
Navigate to **Project > Releases** in order to see the list of releases for a given
project.
![Releases list](img/releases.png)
# Releases
You can turn any git tag into a release, by adding a note to it.
Release notes behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it.
Release notes are stored in the database of GitLab.
NOTE: In GitLab 11.7, we introduced the full fledged [releases](../user/project/releases.md) feature. You can still create release notes on this page, but the new method is preferred.
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-n-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 with GitLab
* In the interface, when you create a new git tag
* In the interface, by adding a note to an existing git tag
* with the GitLab API
* Using the GitLab API
## New tag page with release notes text area
......
......@@ -83,7 +83,7 @@ module Gitlab
def process_external_files(config, project, opts)
sha = opts.fetch(:sha) { project.repository.root_ref_sha }
Config::External::Processor.new(config, project, sha).perform
Config::External::Processor.new(config, project: project, sha: sha).perform
end
end
end
......
......@@ -8,20 +8,26 @@ module Gitlab
class Base
include Gitlab::Utils::StrongMemoize
attr_reader :location, :opts, :errors
attr_reader :location, :params, :context, :errors
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
def initialize(location, opts = {})
@location = location
@opts = opts
Context = Struct.new(:project, :sha)
def initialize(params, context)
@params = params
@context = context
@errors = []
validate!
end
def matching?
location.present?
end
def invalid_extension?
!::File.basename(location).match(YAML_WHITELIST_EXTENSION)
location.nil? || !::File.basename(location).match?(YAML_WHITELIST_EXTENSION)
end
def valid?
......
......@@ -8,11 +8,8 @@ module Gitlab
class Local < Base
include Gitlab::Utils::StrongMemoize
attr_reader :project, :sha
def initialize(location, opts = {})
@project = opts.fetch(:project)
@sha = opts.fetch(:sha)
def initialize(params, context)
@location = params[:local]
super
end
......@@ -32,7 +29,7 @@ module Gitlab
end
def fetch_local_content
project.repository.blob_data_at(sha, location)
context.project.repository.blob_data_at(context.sha, location)
end
end
end
......
......@@ -8,6 +8,12 @@ module Gitlab
class Remote < Base
include Gitlab::Utils::StrongMemoize
def initialize(params, context)
@location = params[:remote]
super
end
def content
strong_memoize(:content) { fetch_remote_content }
end
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
module File
class Template < Base
attr_reader :location, :project
SUFFIX = '.gitlab-ci.yml'.freeze
def initialize(params, context)
@location = params[:template]
super
end
def content
strong_memoize(:content) { fetch_template_content }
end
private
def validate_location!
super
unless template_name_valid?
errors.push("Template file `#{location}` is not a valid location!")
end
end
def template_name
return unless template_name_valid?
location.first(-SUFFIX.length)
end
def template_name_valid?
location.to_s.end_with?(SUFFIX)
end
def fetch_template_content
Gitlab::Template::GitlabCiYmlTemplate.find(template_name, project)&.content
end
end
end
end
end
end
end
......@@ -5,25 +5,63 @@ module Gitlab
class Config
module External
class Mapper
def initialize(values, project, sha)
@locations = Array(values.fetch(:include, []))
include Gitlab::Utils::StrongMemoize
FILE_CLASSES = [
External::File::Remote,
External::File::Template,
External::File::Local
].freeze
AmbigiousSpecificationError = Class.new(StandardError)
def initialize(values, project:, sha:)
@locations = Array.wrap(values.fetch(:include, []))
@project = project
@sha = sha
end
def process
locations.map { |location| build_external_file(location) }
locations
.compact
.map(&method(:normalize_location))
.map(&method(:select_first_matching))
end
private
attr_reader :locations, :project, :sha
attr_reader :locations, :project, :sha, :user
# convert location if String to canonical form
def normalize_location(location)
if location.is_a?(String)
normalize_location_string(location)
else
location.deep_symbolize_keys
end
end
def build_external_file(location)
def normalize_location_string(location)
if ::Gitlab::UrlSanitizer.valid?(location)
External::File::Remote.new(location)
{ remote: location }
else
External::File::Local.new(location, project: project, sha: sha)
{ local: location }
end
end
def select_first_matching(location)
matching = FILE_CLASSES.map do |file_class|
file_class.new(location, context)
end.select(&:matching?)
raise AmbigiousSpecificationError, "Include `#{location.to_json}` needs to match exactly one accessor!" unless matching.one?
matching.first
end
def context
strong_memoize(:context) do
External::File::Base::Context.new(project, sha)
end
end
end
......
......@@ -7,10 +7,12 @@ module Gitlab
class Processor
IncludeError = Class.new(StandardError)
def initialize(values, project, sha)
def initialize(values, project:, sha:)
@values = values
@external_files = External::Mapper.new(values, project, sha).process
@external_files = External::Mapper.new(values, project: project, sha: sha).process
@content = {}
rescue External::Mapper::AmbigiousSpecificationError => e
raise IncludeError, e.message
end
def perform
......
......@@ -7,7 +7,7 @@ module Gitlab
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb|locale/gitlab\.pot}i.freeze
IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb}i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
......
......@@ -15,4 +15,40 @@ describe RunnersHelper do
runner = FactoryBot.build(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
end
describe '#runner_contacted_at' do
let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
let(:contacted_at_cached) { 1.second.ago.change(usec: 0) }
let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
before do
runner.cache_attributes(contacted_at: contacted_at_cached)
end
context 'without sorting' do
it 'returns cached value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
end
end
context 'with sorting set to created_date' do
before do
controller.params[:sort] = 'created_date'
end
it 'returns cached value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
end
end
context 'with sorting set to contacted_asc' do
before do
controller.params[:sort] = 'contacted_asc'
end
it 'returns stored value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_stored)
end
end
end
end
......@@ -3,13 +3,43 @@
require 'fast_spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
subject { described_class.new(location) }
let(:context) { described_class::Context.new(nil, 'HEAD') }
let(:test_class) do
Class.new(described_class) do
def initialize(params, context = {})
@location = params
super
end
end
end
subject { test_class.new(location, context) }
before do
allow_any_instance_of(described_class)
allow_any_instance_of(test_class)
.to receive(:content).and_return('key: value')
end
describe '#matching?' do
context 'when a location is present' do
let(:location) { 'some-location' }
it 'should return true' do
expect(subject).to be_matching
end
end
context 'with a location is missing' do
let(:location) { nil }
it 'should return false' do
expect(subject).not_to be_matching
end
end
end
describe '#valid?' do
context 'when location is not a YAML file' do
let(:location) { 'some/file.txt' }
......@@ -39,7 +69,7 @@ describe Gitlab::Ci::Config::External::File::Base do
let(:location) { 'some/file/config.yml' }
before do
allow_any_instance_of(described_class)
allow_any_instance_of(test_class)
.to receive(:content).and_return('invalid_syntax')
end
......
......@@ -3,8 +3,37 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Local do
let(:project) { create(:project, :repository) }
let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
set(:project) { create(:project, :repository) }
let(:context) { described_class::Context.new(project, '12345') }
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
describe '#matching?' do
context 'when a local is specified' do
let(:params) { { local: 'file' } }
it 'should return true' do
expect(local_file).to be_matching
end
end
context 'with a missing local' do
let(:params) { { local: nil } }
it 'should return false' do
expect(local_file).not_to be_matching
end
end
context 'with a missing local key' do
let(:params) { {} }
it 'should return false' do
expect(local_file).not_to be_matching
end
end
end
describe '#valid?' do
context 'when is a valid local path' do
......
......@@ -3,7 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
let(:remote_file) { described_class.new(location) }
let(:context) { described_class::Context.new(nil, '12345') }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
<<~HEREDOC
......@@ -15,6 +17,32 @@ describe Gitlab::Ci::Config::External::File::Remote do
HEREDOC
end
describe '#matching?' do
context 'when a remote is specified' do
let(:params) { { remote: 'http://remote' } }
it 'should return true' do
expect(remote_file).to be_matching
end
end
context 'with a missing remote' do
let(:params) { { remote: nil } }
it 'should return false' do
expect(remote_file).not_to be_matching
end
end
context 'with a missing remote key' do
let(:params) { {} }
it 'should return false' do
expect(remote_file).not_to be_matching
end
end
end
describe "#valid?" do
context 'when is a valid remote url' do
before do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Template do
let(:context) { described_class::Context.new(nil, '12345') }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
let(:params) { { template: template } }
subject { described_class.new(params, context) }
describe '#matching?' do
context 'when a template is specified' do
let(:params) { { template: 'some-template' } }
it 'should return true' do
expect(subject).to be_matching
end
end
context 'with a missing template' do
let(:params) { { template: nil } }
it 'should return false' do
expect(subject).not_to be_matching
end
end
context 'with a missing template key' do
let(:params) { {} }
it 'should return false' do
expect(subject).not_to be_matching
end
end
end
describe "#valid?" do
context 'when is a valid template name' do
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
it 'should return true' do
expect(subject).to be_valid
end
end
context 'with invalid template name' do
let(:template) { 'Template.yml' }
it 'should return false' do
expect(subject).not_to be_valid
expect(subject.error_message).to include('Template file `Template.yml` is not a valid location!')
end
end
context 'with a non-existing template' do
let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' }
it 'should return false' do
expect(subject).not_to be_valid
expect(subject.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!')
end
end
end
describe '#template_name' do
let(:template_name) { subject.send(:template_name) }
context 'when template does end with .gitlab-ci.yml' do
let(:template) { 'my-template.gitlab-ci.yml' }
it 'returns template name' do
expect(template_name).to eq('my-template')
end
end
context 'when template is nil' do
let(:template) { nil }
it 'returns nil' do
expect(template_name).to be_nil
end
end
context 'when template does not end with .gitlab-ci.yml' do
let(:template) { 'my-template' }
it 'returns nil' do
expect(template_name).to be_nil
end
end
end
end
......@@ -3,84 +3,130 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
let(:project) { create(:project, :repository) }
set(:project) { create(:project, :repository) }
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
let(:file_content) do
<<~HEREDOC
image: 'ruby:2.2'
HEREDOC
end
before do
WebMock.stub_request(:get, remote_url).to_return(body: file_content)
end
describe '#process' do
subject { described_class.new(values, project, '123456').process }
subject { described_class.new(values, project: project, sha: '123456').process }
context "when 'include' keyword is defined as string" do
context "when single 'include' keyword is defined" do
context 'when the string is a local file' do
let(:values) do
{
include: '/lib/gitlab/ci/templates/non-existent-file.yml',
image: 'ruby:2.2'
}
{ include: local_file,
image: 'ruby:2.2' }
end
it 'returns an array' do
expect(subject).to be_an(Array)
it 'returns File instances' do
expect(subject).to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local))
end
end
context 'when the key is a local file hash' do
let(:values) do
{ include: { 'local' => local_file },
image: 'ruby:2.2' }
end
it 'returns File instances' do
expect(subject.first)
.to be_an_instance_of(Gitlab::Ci::Config::External::File::Local)
expect(subject).to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local))
end
end
context 'when the string is a remote file' do
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:values) do
{
include: remote_url,
image: 'ruby:2.2'
}
{ include: remote_url, image: 'ruby:2.2' }
end
before do
WebMock.stub_request(:get, remote_url).to_return(body: file_content)
it 'returns File instances' do
expect(subject).to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Remote))
end
end
context 'when the key is a remote file hash' do
let(:values) do
{ include: { 'remote' => remote_url },
image: 'ruby:2.2' }
end
it 'returns File instances' do
expect(subject).to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Remote))
end
end
it 'returns an array' do
expect(subject).to be_an(Array)
context 'when the key is a template file hash' do
let(:values) do
{ include: { 'template' => template_file },
image: 'ruby:2.2' }
end
it 'returns File instances' do
expect(subject.first)
.to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote)
expect(subject).to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Template))
end
end
context 'when the key is a hash of file and remote' do
let(:values) do
{ include: { 'local' => local_file, 'remote' => remote_url },
image: 'ruby:2.2' }
end
it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
end
end
end
context "when 'include' is defined as an array" do
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:values) do
{
include:
[
remote_url,
'/lib/gitlab/ci/templates/template.yml'
],
image: 'ruby:2.2'
}
{ include: [remote_url, local_file],
image: 'ruby:2.2' }
end
before do
WebMock.stub_request(:get, remote_url).to_return(body: file_content)
it 'returns Files instances' do
expect(subject).to all(respond_to(:valid?))
expect(subject).to all(respond_to(:content))
end
end
it 'returns an array' do
expect(subject).to be_an(Array)
context "when 'include' is defined as an array of hashes" do
let(:values) do
{ include: [{ remote: remote_url }, { local: local_file }],
image: 'ruby:2.2' }
end
it 'returns Files instances' do
expect(subject).to all(respond_to(:valid?))
expect(subject).to all(respond_to(:content))
end
context 'when it has ambigious match' do
let(:values) do
{ include: [{ remote: remote_url, local: local_file }],
image: 'ruby:2.2' }
end
it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
end
end
end
context "when 'include' is not defined" do
......
......@@ -3,8 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
let(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project, '12345') }
set(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project: project, sha: '12345') }
describe "#perform" do
context 'when no external files defined' do
......
......@@ -205,6 +205,23 @@ describe Gitlab::Ci::Config do
end
end
context "when gitlab_ci.yml has ambigious 'include' defined" do
let(:gitlab_ci_yml) do
<<~HEREDOC
include:
remote: http://url
local: /local/file.yml
HEREDOC
end
it 'raises error YamlProcessor validationError' do
expect { config }.to raise_error(
described_class::ConfigError,
'Include `{"remote":"http://url","local":"/local/file.yml"}` needs to match exactly one accessor!'
)
end
end
describe 'external file version' do
context 'when external local file SHA is defined' do
it 'is using a defined value' do
......
......@@ -817,4 +817,13 @@ describe Ci::Runner do
expect(runners).to eq([runner2, runner1])
end
end
describe '#uncached_contacted_at' do
let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
subject { runner.uncached_contacted_at }
it { is_expected.to eq(contacted_at_stored) }
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment