summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrasimir Angelov <kangelov@gitlab.com>2019-04-26 11:16:12 +1200
committerKrasimir Angelov <kangelov@gitlab.com>2019-04-26 12:29:07 +1200
commit28c5261a9e56f6e7160f0b9fd01395257254c6ab (patch)
tree98e36dad6fbb01654d86573fa301baa356d91d56
parent583fc21c3812c0682f780253b2607a8a3f763e39 (diff)
downloadgitlab-ce-56838-api-get-release-by-id.tar.gz
Add API endpoint to get single release by id56838-api-get-release-by-id
Introduce `GET /projects/:id/releases/:release_id` and deprecate `GET /projects/:id/releases/:tag_name`. Add `id` to release entity.
-rw-r--r--doc/api/releases/index.md89
-rw-r--r--doc/api/tags.md4
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/releases.rb36
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/tag_release.json2
-rw-r--r--spec/requests/api/releases_spec.rb163
8 files changed, 288 insertions, 14 deletions
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index e7f79a0d359..8762aad4225 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -27,6 +27,7 @@ Example response:
```json
[
{
+ "id": 42,
"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",
@@ -93,6 +94,7 @@ Example response:
}
},
{
+ "id": 43,
"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",
@@ -154,6 +156,9 @@ Example response:
Get a Release for the given tag.
+CAUTION: **Warning:**
+This endpoint has been deprecated in Gitlab 11.11 and will be removed in 12.0. Switch to using `GET /projects/:id/releases/:release_id` instead.
+
```
GET /projects/:id/releases/:tag_name
```
@@ -173,6 +178,87 @@ Example response:
```json
{
+ "id": 42,
+ "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 single release
+
+Get a specific release for given id.
+
+```
+GET /projects/:id/releases/:release_id
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
+| `release_id` | integer/string | yes | The release id. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/123"
+```
+
+Example response:
+
+```json
+{
+ "id": 42,
"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",
@@ -260,6 +346,7 @@ Example response:
```json
{
+ "id": 42,
"tag_name":"v0.3",
"description":"Super nice release",
"name":"New release",
@@ -346,6 +433,7 @@ Example response:
```json
{
+ "id": 42,
"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",
@@ -425,6 +513,7 @@ Example response:
```json
{
+ "id": 42,
"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",
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 3177fec618f..645e338e736 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -41,6 +41,7 @@ Parameters:
"committed_date": "2012-05-28T04:42:42-07:00"
},
"release": {
+ "id": 42,
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
},
@@ -133,6 +134,7 @@ Parameters:
"committed_date": "2012-05-28T04:42:42-07:00"
},
"release": {
+ "id": 42,
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
},
@@ -191,6 +193,7 @@ Response:
```json
{
+ "id": 42,
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
@@ -223,6 +226,7 @@ Response:
```json
{
+ "id": 42,
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index e25401b6260..c493c45a874 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1130,6 +1130,7 @@ module API
# deprecated old Release representation
class TagRelease < Grape::Entity
+ expose :id
expose :tag, as: :tag_name
expose :description
end
@@ -1149,6 +1150,7 @@ module API
end
class Release < Grape::Entity
+ expose :id
expose :name
expose :tag, as: :tag_name, if: -> (release, _) { can_download_code?(release.project) }
expose :description
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 6b17f4317db..94f73bb156b 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -27,16 +27,28 @@ module API
end
desc 'Get a single project release' do
- detail 'This feature was introduced in GitLab 11.7.'
+ detail 'Finding release by tag name has been deprecated in Gitlab 11.11 and will be removed in 12.0. Switch to using `GET /projects/:id/releases/:release_id` instead.'
success Entities::Release
end
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
- authorize_download_code!
-
- present release, with: Entities::Release, current_user: current_user
+ # Deprecate `:id/releases/:tag_name` in GitLab 11.11.
+ # We want to introduce `:id/releases/:release_id` which is basicaly the same route,
+ # so for the time being we need to make this endpoint support both.
+ # If release with such tag exists behave like the old endpoint,
+ # otherwise act like looking up release by id.
+ # Cleanup once we remove `:id/releases/:tag_name` in GitLab 12.0.
+ if release_by_tag
+ authorize! :download_code, release_by_tag
+ present release_by_tag, with: Entities::Release, current_user: current_user
+ elsif release_by_id
+ authorize! :read_release, release_by_id
+ present release_by_id, with: Entities::Release, current_user: current_user
+ else
+ forbidden!
+ end
end
desc 'Create a new release' do
@@ -123,10 +135,6 @@ module API
authorize! :read_release, user_project
end
- def authorize_read_release!
- authorize! :read_release, release
- end
-
def authorize_update_release!
authorize! :update_release, release
end
@@ -135,13 +143,17 @@ module API
authorize! :destroy_release, release
end
- def authorize_download_code!
- authorize! :download_code, release
- end
-
def release
@release ||= user_project.releases.find_by_tag(params[:tag])
end
+
+ def release_by_tag
+ release
+ end
+
+ def release_by_id
+ @release_by_id ||= user_project.releases.find_by_id(params[:tag])
+ end
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index 6ea0781c1ed..a6ab0f647bd 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -1,7 +1,8 @@
{
"type": "object",
- "required": ["name", "tag_name", "commit"],
+ "required": ["id", "name", "tag_name", "commit"],
"properties": {
+ "id": { "type": "integer" },
"name": { "type": "string" },
"tag_name": { "type": "string" },
"description": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
index e78398ad1d5..54cd83cb68c 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
@@ -1,7 +1,8 @@
{
"type": "object",
- "required": ["name"],
+ "required": ["id", "name"],
"properties": {
+ "id": { "type": "integer" },
"name": { "type": "string" },
"description": { "type": "string" },
"description_html": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
index 6612c2a9911..635b4323458 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
@@ -1,10 +1,12 @@
{
"type": "object",
"required" : [
+ "id",
"tag_name",
"description"
],
"properties" : {
+ "id": { "type": "integer" },
"tag_name": { "type": ["string", "null"] },
"description": { "type": "string" }
},
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 8603fa2a73d..5efd480868f 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -291,6 +291,169 @@ describe API::Releases do
end
end
+ describe 'GET /projects/:id/releases/:id' do
+ context 'when there is a release' do
+ let!(:release) do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ sha: commit.id,
+ author: maintainer,
+ description: 'This is v0.1')
+ end
+
+ it 'returns 200 HTTP status' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns a release entry' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(json_response['tag_name']).to eq(release.tag)
+ expect(json_response['description']).to eq('This is v0.1')
+ expect(json_response['author']['name']).to eq(maintainer.name)
+ expect(json_response['commit']['id']).to eq(commit.id)
+ expect(json_response['assets']['count']).to eq(4)
+ end
+
+ it 'matches response schema' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(response).to match_response_schema('public_api/v4/release')
+ end
+
+ it 'contains source information as assets' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(json_response['assets']['sources'].map { |h| h['format'] })
+ .to match_array(release.sources.map(&:format))
+ expect(json_response['assets']['sources'].map { |h| h['url'] })
+ .to match_array(release.sources.map(&:url))
+ end
+
+ context "when release description contains confidential issue's link" do
+ let(:confidential_issue) do
+ create(:issue,
+ :confidential,
+ project: project,
+ title: 'A vulnerability')
+ end
+
+ let!(:release) do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ sha: commit.id,
+ author: maintainer,
+ description: "This is confidential #{confidential_issue.to_reference}")
+ end
+
+ it "does not expose confidential issue's title" do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(json_response['description_html']).to include(confidential_issue.to_reference)
+ expect(json_response['description_html']).not_to include('A vulnerability')
+ end
+ end
+
+ context 'when release has link asset' do
+ let!(:link) do
+ create(:release_link,
+ release: release,
+ name: 'release-18.04.dmg',
+ url: url)
+ end
+
+ let(:url) { 'https://my-external-hosting.example.com/scrambled-url/app.zip' }
+
+ it 'contains link information as assets' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(json_response['assets']['links'].count).to eq(1)
+ expect(json_response['assets']['links'].first['id']).to eq(link.id)
+ expect(json_response['assets']['links'].first['name'])
+ .to eq('release-18.04.dmg')
+ expect(json_response['assets']['links'].first['url'])
+ .to eq('https://my-external-hosting.example.com/scrambled-url/app.zip')
+ expect(json_response['assets']['links'].first['external'])
+ .to be_truthy
+ end
+
+ context 'when link is internal' do
+ let(:url) do
+ "#{project.web_url}/-/jobs/artifacts/v11.6.0-rc4/download?" \
+ "job=rspec-mysql+41%2F50"
+ end
+
+ it 'has external false' do
+ get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+ expect(json_response['assets']['links'].first['external'])
+ .to be_falsy
+ end
+ end
+ end
+
+ context 'when user is a guest' do
+ it 'responds 200 OK' do
+ get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it "does not expose tag, commit and source code" do
+ get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+ expect(response).to match_response_schema('public_api/v4/release/release_for_guest')
+ expect(json_response['assets']['count']).to eq(release.links.count)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'responds 200 OK' do
+ get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it "exposes tag and commit" do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ author: maintainer,
+ created_at: 2.days.ago)
+ get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+ expect(response).to match_response_schema('public_api/v4/release')
+ end
+ end
+ end
+ end
+
+ context 'when user is not a project member' do
+ let!(:release) { create(:release, tag: 'v0.1', project: project) }
+
+ it 'cannot find the project' do
+ get api("/projects/#{project.id}/releases/#{release.id}", non_project_member)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'allows the request' do
+ get api("/projects/#{project.id}/releases/#{release.id}", non_project_member)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
describe 'POST /projects/:id/releases' do
let(:params) do
{