summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2017-05-31 22:58:34 +0800
committerLin Jen-Shin <godfat@godfat.org>2017-05-31 22:58:34 +0800
commitafb1bf0baf23a6e7b111fb5b305c16c38cf3a99e (patch)
tree6f7981f1d809e205df6bbe13638c28d91f3caeed
parent258cdd14b557b96c5340ea91c6915b8787ce1b1e (diff)
parent4ad85b22e25ab9b2d089fce7138f07c72c5fd5d6 (diff)
downloadgitlab-ce-afb1bf0baf23a6e7b111fb5b305c16c38cf3a99e.tar.gz
Merge remote-tracking branch 'upstream/master' into rename-builds-controller
* upstream/master: (63 commits) Update docs related to protected actions Add changelog for protected branches abilities fix Ask for an example project for bug reports Center loading spinner in issuable filters Fix chat commands specs related to protected actions Fix builds controller specs related to protected actions Fix pipeline retry specs related to protected actions Fix environment model specs related to protected actions Fix build factory specs related to protected actions Fix job play service specs related to protected actions Fix play status specs related to protected actions Fix deploy chat command specs for protected actions Fix environment specs related to protected actions Fix pipeline processing specs related to protected actions Fix build entity specs related to protected actions Check only a merge ability for protected actions Add tag_list param to project api Allow PostReceivePack to be enabled with Gitaly Remove some deprecated methods Add :owned param to ProjectFinder ...
-rw-r--r--.gitlab/issue_templates/Bug.md6
-rw-r--r--app/assets/stylesheets/framework/filters.scss1
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/finders/projects_finder.rb33
-rw-r--r--app/models/ci/pipeline_schedule.rb4
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/user.rb6
-rw-r--r--app/policies/ci/build_policy.rb2
-rw-r--r--app/views/projects/deployments/_actions.haml1
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml4
-rw-r--r--changelogs/unreleased/33000-tag-list-in-project-create-api.yml4
-rw-r--r--changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml4
-rw-r--r--changelogs/unreleased/issue-template-reproduce-in-example-project.yml4
-rw-r--r--changelogs/unreleased/tc-improve-project-api-perf.yml4
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/pipeline_schedules.md273
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb11
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/helpers.rb34
-rw-r--r--lib/api/pipeline_schedules.rb131
-rw-r--r--lib/api/projects.rb28
-rw-r--r--lib/api/v3/helpers.rb27
-rw-r--r--lib/api/v3/projects.rb2
-rw-r--r--lib/gitlab/workhorse.rb3
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb6
-rw-r--r--spec/features/projects/environments/environment_spec.rb57
-rw-r--r--spec/finders/projects_finder_spec.rb15
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json41
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedules.json4
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb7
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb10
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/models/environment_spec.rb5
-rw-r--r--spec/models/project_spec.rb14
-rw-r--r--spec/models/user_spec.rb19
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb297
-rw-r--r--spec/requests/api/projects_spec.rb8
-rw-r--r--spec/serializers/build_entity_spec.rb6
-rw-r--r--spec/services/ci/play_build_service_spec.rb17
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb7
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb7
47 files changed, 1011 insertions, 125 deletions
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 58af062e75e..9d53a48409a 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate.
(How one can reproduce the issue - this is very important)
+### Example Project
+
+(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
+
+(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
+
### What is the current *bug* behavior?
(What actually happens)
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index d191bbb226c..90051ffe753 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -475,4 +475,5 @@
.filter-dropdown-loading {
padding: 8px 16px;
+ text-align: center;
}
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 6195121b931..f9c31920302 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events
projects =
if params[:filter] == "starred"
- current_user.viewable_starred_projects
+ ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
else
current_user.authorized_projects
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index f6d8226bf3f..5bf722d1ec6 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -7,6 +7,7 @@
# project_ids_relation: int[] - project ids to use
# params:
# trending: boolean
+# owned: boolean
# non_public: boolean
# starred: boolean
# sort: string
@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder
def execute
items = init_collection
- items = by_ids(items)
+ items = items.map do |item|
+ item = by_ids(item)
+ item = by_personal(item)
+ item = by_starred(item)
+ item = by_trending(item)
+ item = by_visibilty_level(item)
+ item = by_tags(item)
+ item = by_search(item)
+ by_archived(item)
+ end
items = union(items)
- items = by_personal(items)
- items = by_visibilty_level(items)
- items = by_tags(items)
- items = by_search(items)
- items = by_archived(items)
sort(items)
end
@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder
def init_collection
projects = []
- if params[:trending].present?
- projects << Project.trending
- elsif params[:starred].present? && current_user
- projects << current_user.viewable_starred_projects
+ if params[:owned].present?
+ projects << current_user.owned_projects if current_user
else
projects << current_user.authorized_projects if current_user
projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder
end
def by_ids(items)
- project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items
+ project_ids_relation ? items.where(id: project_ids_relation) : items
end
def union(items)
@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder
(params[:personal].present? && current_user) ? items.personal(current_user) : items
end
+ def by_starred(items)
+ (params[:starred].present? && current_user) ? items.starred_by(current_user) : items
+ end
+
+ def by_trending(items)
+ params[:trending].present? ? items.trending : items
+ end
+
def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 07213ca608a..45d8cd34359 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -24,6 +24,10 @@ module Ci
owner == current_user
end
+ def own!(user)
+ update(owner: user)
+ end
+
def inactive?
!active?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index a59095cb25c..457399cb60e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -242,6 +242,7 @@ class Project < ActiveRecord::Base
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
+ scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
@@ -350,10 +351,6 @@ class Project < ActiveRecord::Base
where("projects.id IN (#{union.to_sql})")
end
- def search_by_visibility(level)
- where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
- end
-
def search_by_title(query)
pattern = "%#{query}%"
table = Project.arel_table
diff --git a/app/models/user.rb b/app/models/user.rb
index 3f816a250c2..9aad327b592 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -557,12 +557,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end
- def viewable_starred_projects
- starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (?)",
- [Project::PUBLIC, Project::INTERNAL],
- authorized_projects.select(:project_id))
- end
-
def owned_projects
@owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?',
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index d4af4490608..2d7405dc240 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -23,7 +23,7 @@ module Ci
!::Gitlab::UserAccess
.new(user, project: build.project)
- .can_push_to_branch?(build.ref)
+ .can_merge_to_branch?(build.ref)
end
end
end
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 506246f2ee6..e2baaa625ae 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -8,6 +8,7 @@
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
+ - next unless can?(current_user, :update_build, action)
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7315e671056..9e221240cf2 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -13,7 +13,7 @@
= render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment)
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
- - if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
+ - if can?(current_user, :stop_environment, @environment)
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.environments-container
diff --git a/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml b/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml
new file mode 100644
index 00000000000..26ce84697d0
--- /dev/null
+++ b/changelogs/unreleased/30892-add-api-support-for-pipeline-schedule.yml
@@ -0,0 +1,4 @@
+---
+title: Add API support for pipeline schedule
+merge_request: 11307
+author: dosuken123
diff --git a/changelogs/unreleased/33000-tag-list-in-project-create-api.yml b/changelogs/unreleased/33000-tag-list-in-project-create-api.yml
new file mode 100644
index 00000000000..b0d0d3cbeba
--- /dev/null
+++ b/changelogs/unreleased/33000-tag-list-in-project-create-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add tag_list param to project api
+merge_request: 11799
+author: Ivan Chernov
diff --git a/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml b/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml
new file mode 100644
index 00000000000..43c18502cd6
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml
@@ -0,0 +1,4 @@
+---
+title: Respect merge, instead of push, permissions for protected actions
+merge_request: 11648
+author:
diff --git a/changelogs/unreleased/issue-template-reproduce-in-example-project.yml b/changelogs/unreleased/issue-template-reproduce-in-example-project.yml
new file mode 100644
index 00000000000..8116007b459
--- /dev/null
+++ b/changelogs/unreleased/issue-template-reproduce-in-example-project.yml
@@ -0,0 +1,4 @@
+---
+title: Ask for an example project for bug reports
+merge_request:
+author:
diff --git a/changelogs/unreleased/tc-improve-project-api-perf.yml b/changelogs/unreleased/tc-improve-project-api-perf.yml
new file mode 100644
index 00000000000..7e88466c058
--- /dev/null
+++ b/changelogs/unreleased/tc-improve-project-api-perf.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of ProjectFinder used in /projects API endpoint
+merge_request: 11666
+author:
diff --git a/doc/api/README.md b/doc/api/README.md
index 1b0f6470b13..45579ccac4e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -33,6 +33,7 @@ following locations:
- [Notification settings](notification_settings.md)
- [Pipelines](pipelines.md)
- [Pipeline Triggers](pipeline_triggers.md)
+- [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
- [Project Members](members.md)
diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md
new file mode 100644
index 00000000000..433654c18cc
--- /dev/null
+++ b/doc/api/pipeline_schedules.md
@@ -0,0 +1,273 @@
+# Pipeline schedules
+
+You can read more about [pipeline schedules](../user/project/pipelines/schedules.md).
+
+## Get all pipeline schedules
+
+Get a list of the pipeline schedules of a project.
+
+```
+GET /projects/:id/pipeline_schedules
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `scope` | string | no | The scope of pipeline schedules, one of: `active`, `inactive` |
+
+```sh
+curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules"
+```
+
+```json
+[
+ {
+ "id": 13,
+ "description": "Test schedule pipeline",
+ "ref": "master",
+ "cron": "* * * * *",
+ "cron_timezone": "Asia/Tokyo",
+ "next_run_at": "2017-05-19T13:41:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:31:08.849Z",
+ "updated_at": "2017-05-19T13:40:17.727Z",
+ "owner": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ }
+ }
+]
+```
+
+## Get a single pipeline schedule
+
+Get the pipeline schedule of a project.
+
+```
+GET /projects/:id/pipeline_schedules/:pipeline_schedule_id
+```
+
+| Attribute | Type | required | Description |
+|--------------|---------|----------|--------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+
+```sh
+curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
+```
+
+```json
+{
+ "id": 13,
+ "description": "Test schedule pipeline",
+ "ref": "master",
+ "cron": "* * * * *",
+ "cron_timezone": "Asia/Tokyo",
+ "next_run_at": "2017-05-19T13:41:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:31:08.849Z",
+ "updated_at": "2017-05-19T13:40:17.727Z",
+ "last_pipeline": {
+ "id": 332,
+ "sha": "0e788619d0b5ec17388dffb973ecd505946156db",
+ "ref": "master",
+ "status": "pending"
+ },
+ "owner": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ }
+}
+```
+
+## Create a new pipeline schedule
+
+Create a new pipeline schedule of a project.
+
+```
+POST /projects/:id/pipeline_schedules
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `description` | string | yes | The description of pipeline schedule |
+| `ref` | string | yes | The branch/tag name will be triggered |
+| `cron ` | string | yes | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) |
+| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`) |
+| `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially (default: `true`) |
+
+```sh
+curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form description="Build packages" --form ref="master" --form cron="0 1 * * 5" --form cron_timezone="UTC" --form active="true" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules"
+```
+
+```json
+{
+ "id": 14,
+ "description": "Build packages",
+ "ref": "master",
+ "cron": "0 1 * * 5",
+ "cron_timezone": "UTC",
+ "next_run_at": "2017-05-26T01:00:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:43:08.169Z",
+ "updated_at": "2017-05-19T13:43:08.169Z",
+ "last_pipeline": null,
+ "owner": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ }
+}
+```
+
+## Edit a pipeline schedule
+
+Updates the pipeline schedule of a project. Once the update is done, it will be rescheduled automatically.
+
+```
+PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+| `description` | string | no | The description of pipeline schedule |
+| `ref` | string | no | The branch/tag name will be triggered |
+| `cron ` | string | no | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) |
+| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) or `TZInfo::Timezone` (e.g. `America/Los_Angeles`) |
+| `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially. |
+
+```sh
+curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form cron="0 2 * * *" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
+```
+
+```json
+{
+ "id": 13,
+ "description": "Test schedule pipeline",
+ "ref": "master",
+ "cron": "0 2 * * *",
+ "cron_timezone": "Asia/Tokyo",
+ "next_run_at": "2017-05-19T17:00:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:31:08.849Z",
+ "updated_at": "2017-05-19T13:44:16.135Z",
+ "last_pipeline": {
+ "id": 332,
+ "sha": "0e788619d0b5ec17388dffb973ecd505946156db",
+ "ref": "master",
+ "status": "pending"
+ },
+ "owner": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ }
+}
+```
+
+## Take ownership of a pipeline schedule
+
+Update the owner of the pipeline schedule of a project.
+
+```
+POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+
+```sh
+curl --request POST --header "PRIVATE-TOKEN: hf2CvZXB9w8Uc5pZKpSB" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/take_ownership"
+```
+
+```json
+{
+ "id": 13,
+ "description": "Test schedule pipeline",
+ "ref": "master",
+ "cron": "0 2 * * *",
+ "cron_timezone": "Asia/Tokyo",
+ "next_run_at": "2017-05-19T17:00:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:31:08.849Z",
+ "updated_at": "2017-05-19T13:46:37.468Z",
+ "last_pipeline": {
+ "id": 332,
+ "sha": "0e788619d0b5ec17388dffb973ecd505946156db",
+ "ref": "master",
+ "status": "pending"
+ },
+ "owner": {
+ "name": "shinya",
+ "username": "maeda",
+ "id": 50,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/8ca0a796a679c292e3a11da50f99e801?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/maeda"
+ }
+}
+```
+
+## Delete a pipeline schedule
+
+Delete the pipeline schedule of a project.
+
+```
+DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id
+```
+
+| Attribute | Type | required | Description |
+|----------------|---------|----------|--------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
+```
+
+```json
+{
+ "id": 13,
+ "description": "Test schedule pipeline",
+ "ref": "master",
+ "cron": "0 2 * * *",
+ "cron_timezone": "Asia/Tokyo",
+ "next_run_at": "2017-05-19T17:00:00.000Z",
+ "active": true,
+ "created_at": "2017-05-19T13:31:08.849Z",
+ "updated_at": "2017-05-19T13:46:37.468Z",
+ "last_pipeline": {
+ "id": 332,
+ "sha": "0e788619d0b5ec17388dffb973ecd505946156db",
+ "ref": "master",
+ "status": "pending"
+ },
+ "owner": {
+ "name": "shinya",
+ "username": "maeda",
+ "id": 50,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/8ca0a796a679c292e3a11da50f99e801?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/maeda"
+ }
+}
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 345f93a6017..62c78ddc32e 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -473,6 +473,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
+| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Create project for user
@@ -506,6 +507,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
+| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Edit project
@@ -538,6 +540,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
+| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Fork project
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index da20076da52..2df03196f80 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -591,7 +591,7 @@ Optional manual actions have `allow_failure: true` set by default.
**Manual actions are considered to be write actions, so permissions for
protected branches are used when user wants to trigger an action. In other
words, in order to trigger a manual action assigned to a branch that the
-pipeline is running for, user needs to have ability to push to this branch.**
+pipeline is running for, user needs to have ability to merge to this branch.**
### environment
diff --git a/lib/api/api.rb b/lib/api/api.rb
index ac113c5200d..bbdd2039f43 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -110,6 +110,7 @@ module API
mount ::API::Notes
mount ::API::NotificationSettings
mount ::API::Pipelines
+ mount ::API::PipelineSchedules
mount ::API::ProjectHooks
mount ::API::Projects
mount ::API::ProjectSnippets
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 8c5e5c91769..e10bd230ae2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -686,6 +686,17 @@ module API
expose :coverage
end
+ class PipelineSchedule < Grape::Entity
+ expose :id
+ expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active
+ expose :created_at, :updated_at
+ expose :owner, using: Entities::UserBasic
+ end
+
+ class PipelineScheduleDetails < PipelineSchedule
+ expose :last_pipeline, using: Entities::PipelineBasic
+ end
+
class EnvironmentBasic < Grape::Entity
expose :id, :name, :slug, :external_url
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ee85b777aff..e14a988a153 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -151,8 +151,8 @@ module API
end
get ":id/projects" do
group = find_group!(params[:id])
- projects = GroupProjectsFinder.new(group: group, current_user: current_user).execute
- projects = filter_projects(projects)
+ projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
present paginate(projects), with: entity, current_user: current_user
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 226a7ddd50e..d61450f8258 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -256,31 +256,21 @@ module API
# project helpers
- def filter_projects(projects)
- if params[:membership]
- projects = projects.merge(current_user.authorized_projects)
- end
-
- if params[:owned]
- projects = projects.merge(current_user.owned_projects)
- end
-
- if params[:starred]
- projects = projects.merge(current_user.starred_projects)
- end
-
- if params[:search].present?
- projects = projects.search(params[:search])
- end
-
- if params[:visibility].present?
- projects = projects.search_by_visibility(params[:visibility])
- end
-
- projects = projects.where(archived: params[:archived])
+ def reorder_projects(projects)
projects.reorder(params[:order_by] => params[:sort])
end
+ def project_finder_params
+ finder_params = {}
+ finder_params[:owned] = true if params[:owned].present?
+ finder_params[:non_public] = true if params[:membership].present?
+ finder_params[:starred] = true if params[:starred].present?
+ finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
+ finder_params[:archived] = params[:archived]
+ finder_params[:search] = params[:search] if params[:search]
+ finder_params
+ end
+
# file helpers
def uploaded_file(field, uploads_path)
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
new file mode 100644
index 00000000000..93d89209934
--- /dev/null
+++ b/lib/api/pipeline_schedules.rb
@@ -0,0 +1,131 @@
+module API
+ class PipelineSchedules < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
+ desc 'Get all pipeline schedules' do
+ success Entities::PipelineSchedule
+ end
+ params do
+ use :pagination
+ optional :scope, type: String, values: %w[active inactive],
+ desc: 'The scope of pipeline schedules'
+ end
+ get ':id/pipeline_schedules' do
+ authorize! :read_pipeline_schedule, user_project
+
+ schedules = PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
+ .preload([:owner, :last_pipeline])
+ present paginate(schedules), with: Entities::PipelineSchedule
+ end
+
+ desc 'Get a single pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ get ':id/pipeline_schedules/:pipeline_schedule_id' do
+ authorize! :read_pipeline_schedule, user_project
+
+ not_found!('PipelineSchedule') unless pipeline_schedule
+
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ end
+
+ desc 'Create a new pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :description, type: String, desc: 'The description of pipeline schedule'
+ requires :ref, type: String, desc: 'The branch/tag name will be triggered'
+ requires :cron, type: String, desc: 'The cron'
+ optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone'
+ optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule'
+ end
+ post ':id/pipeline_schedules' do
+ authorize! :create_pipeline_schedule, user_project
+
+ pipeline_schedule = Ci::CreatePipelineScheduleService
+ .new(user_project, current_user, declared_params(include_missing: false))
+ .execute
+
+ if pipeline_schedule.persisted?
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Edit a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ optional :description, type: String, desc: 'The description of pipeline schedule'
+ optional :ref, type: String, desc: 'The branch/tag name will be triggered'
+ optional :cron, type: String, desc: 'The cron'
+ optional :cron_timezone, type: String, desc: 'The timezone'
+ optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id' do
+ authorize! :update_pipeline_schedule, user_project
+
+ not_found!('PipelineSchedule') unless pipeline_schedule
+
+ if pipeline_schedule.update(declared_params(include_missing: false))
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Take ownership of a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ authorize! :update_pipeline_schedule, user_project
+
+ not_found!('PipelineSchedule') unless pipeline_schedule
+
+ if pipeline_schedule.own!(current_user)
+ present pipeline_schedule, with: Entities::PipelineScheduleDetails
+ else
+ render_validation_error!(pipeline_schedule)
+ end
+ end
+
+ desc 'Delete a pipeline schedule' do
+ success Entities::PipelineScheduleDetails
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id' do
+ authorize! :admin_pipeline_schedule, user_project
+
+ not_found!('PipelineSchedule') unless pipeline_schedule
+
+ status :accepted
+ present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
+ end
+ end
+
+ helpers do
+ def pipeline_schedule
+ @pipeline_schedule ||=
+ user_project.pipeline_schedules
+ .preload(:owner, :last_pipeline)
+ .find_by(id: params.delete(:pipeline_schedule_id))
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d4fe5c023bf..d00d4fe1737 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -21,6 +21,7 @@ module API
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
+ optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
end
params :optional_params do
@@ -67,20 +68,19 @@ module API
optional :import_url, type: String, desc: 'URL from which the project is imported'
end
- def present_projects(projects, options = {})
+ def present_projects(options = {})
+ projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ projects = projects.with_statistics if params[:statistics]
+ projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+
options = options.reverse_merge(
- with: Entities::Project,
- current_user: current_user,
- simple: params[:simple],
- with_issues_enabled: params[:with_issues_enabled],
- with_merge_requests_enabled: params[:with_merge_requests_enabled]
+ with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
+ statistics: params[:statistics],
+ current_user: current_user
)
-
- projects = filter_projects(projects)
- projects = projects.with_statistics if options[:statistics]
- projects = projects.with_issues_enabled if options[:with_issues_enabled]
- projects = projects.with_merge_requests_enabled if options[:with_merge_requests_enabled]
- options[:with] = Entities::BasicProjectDetails if options[:simple]
+ options[:with] = Entities::BasicProjectDetails if params[:simple]
present paginate(projects), options
end
@@ -94,8 +94,7 @@ module API
use :statistics_params
end
get do
- entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
- present_projects ProjectsFinder.new(current_user: current_user).execute, with: entity, statistics: params[:statistics]
+ present_projects
end
desc 'Create new project' do
@@ -231,6 +230,7 @@ module API
:request_access_enabled,
:shared_runners_enabled,
:snippets_enabled,
+ :tag_list,
:visibility,
:wiki_enabled
]
diff --git a/lib/api/v3/helpers.rb b/lib/api/v3/helpers.rb
index 0f234d4cdad..d9e76560d03 100644
--- a/lib/api/v3/helpers.rb
+++ b/lib/api/v3/helpers.rb
@@ -14,6 +14,33 @@ module API
authorize! access_level, merge_request
merge_request
end
+
+ # project helpers
+
+ def filter_projects(projects)
+ if params[:membership]
+ projects = projects.merge(current_user.authorized_projects)
+ end
+
+ if params[:owned]
+ projects = projects.merge(current_user.owned_projects)
+ end
+
+ if params[:starred]
+ projects = projects.merge(current_user.starred_projects)
+ end
+
+ if params[:search].present?
+ projects = projects.search(params[:search])
+ end
+
+ if params[:visibility].present?
+ projects = projects.where(visibility_level: Gitlab::VisibilityLevel.level_value(params[:visibility]))
+ end
+
+ projects = projects.where(archived: params[:archived])
+ projects.reorder(params[:order_by] => params[:sort])
+ end
end
end
end
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 164612cb8dd..896c00b88e7 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -147,7 +147,7 @@ module API
get '/starred' do
authenticate!
- present_projects current_user.viewable_starred_projects
+ present_projects ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
end
desc 'Get all projects for admin user' do
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index fe37e4da94f..18d8b4f4744 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -31,8 +31,7 @@ module Gitlab
feature_enabled = case action.to_s
when 'git_receive_pack'
- # Disabled for now, see https://gitlab.com/gitlab-org/gitaly/issues/172
- false
+ Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
when 'git_upload_pack'
Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
when 'info_refs'
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index d011ce70244..838bdae1445 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -234,7 +234,11 @@ describe Projects::JobsController do
describe 'POST play' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
+
sign_in(user)
post_play
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 86ce50c976f..18b608c863e 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -12,6 +12,7 @@ feature 'Environment', :feature do
feature 'environment details page' do
given!(:environment) { create(:environment, project: project) }
+ given!(:permissions) { }
given!(:deployment) { }
given!(:action) { }
@@ -62,20 +63,31 @@ feature 'Environment', :feature do
name: 'deploy to production')
end
- given(:role) { :master }
+ context 'when user has ability to trigger deployment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does show a play button' do
- expect(page).to have_link(action.name.humanize)
- end
+ it 'does show a play button' do
+ expect(page).to have_link(action.name.humanize)
+ end
+
+ it 'does allow to play manual action' do
+ expect(action).to be_manual
- scenario 'does allow to play manual action' do
- expect(action).to be_manual
+ expect { click_link(action.name.humanize) }
+ .not_to change { Ci::Pipeline.count }
- expect { click_link(action.name.humanize) }
- .not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(action.name)
+ expect(action.reload).to be_pending
+ end
+ end
- expect(page).to have_content(action.name)
- expect(action.reload).to be_pending
+ context 'when user has no ability to trigger a deployment' do
+ it 'does not show a play button' do
+ expect(page).not_to have_link(action.name.humanize)
+ end
end
context 'with external_url' do
@@ -134,12 +146,23 @@ feature 'Environment', :feature do
on_stop: 'close_app')
end
- given(:role) { :master }
+ context 'when user has ability to stop environment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does allow to stop environment' do
- click_link('Stop')
+ it 'allows to stop environment' do
+ click_link('Stop')
- expect(page).to have_content('close_app')
+ expect(page).to have_content('close_app')
+ end
+ end
+
+ context 'when user has no ability to stop environment' do
+ it 'does not allow to stop environment' do
+ expect(page).to have_no_link('Stop')
+ end
end
context 'for reporter' do
@@ -150,12 +173,6 @@ feature 'Environment', :feature do
end
end
end
-
- context 'without stop action' do
- scenario 'does allow to stop environment' do
- click_link('Stop')
- end
- end
end
context 'when environment is stopped' do
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 148adcffe3b..03d98459e8c 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -137,6 +137,13 @@ describe ProjectsFinder do
it { is_expected.to eq([public_project]) }
end
+ describe 'filter by owned' do
+ let(:params) { { owned: true } }
+ let!(:owned_project) { create(:empty_project, :private, namespace: current_user.namespace) }
+
+ it { is_expected.to eq([owned_project]) }
+ end
+
describe 'filter by non_public' do
let(:params) { { non_public: true } }
before do
@@ -146,13 +153,19 @@ describe ProjectsFinder do
it { is_expected.to eq([private_project]) }
end
- describe 'filter by viewable_starred_projects' do
+ describe 'filter by starred' do
let(:params) { { starred: true } }
before do
current_user.toggle_star(public_project)
end
it { is_expected.to eq([public_project]) }
+
+ it 'returns only projects the user has access to' do
+ current_user.toggle_star(private_project)
+
+ is_expected.to eq([public_project])
+ end
end
describe 'sorting' do
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
new file mode 100644
index 00000000000..f6346bd0fb6
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -0,0 +1,41 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "description": { "type": "string" },
+ "ref": { "type": "string" },
+ "cron": { "type": "string" },
+ "cron_timezone": { "type": "string" },
+ "next_run_at": { "type": "date" },
+ "active": { "type": "boolean" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "last_pipeline": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "sha": { "type": "string" },
+ "ref": { "type": "string" },
+ "status": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "owner": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "id", "description", "ref", "cron", "cron_timezone", "next_run_at",
+ "active", "created_at", "updated_at", "owner"
+ ],
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/pipeline_schedules.json b/spec/fixtures/api/schemas/pipeline_schedules.json
new file mode 100644
index 00000000000..173a28d2505
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedules.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "pipeline_schedule.json" }
+}
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index eb4f06b371c..13e6953147b 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -58,9 +58,12 @@ describe Gitlab::ChatCommands::Command, service: true do
end
end
- context 'and user does have deployment permission' do
+ context 'and user has deployment permission' do
before do
- build.project.add_master(user)
+ build.project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'returns action' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index b33389d959e..46dbdeae37c 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -7,7 +7,12 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let(:regex_match) { described_class.match('deploy staging to production') }
before do
- project.add_master(user)
+ # Make it possible to trigger protected manual actions for developers.
+ #
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
subject do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 185bb9098da..3f30b2c38f2 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -224,7 +224,10 @@ describe Gitlab::Ci::Status::Build::Factory do
context 'when user has ability to play action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'fabricates status that has action' do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f5d0f977768..0e15a5f3c6b 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) }
+ let(:project) { build.project }
let(:build) { create(:ci_build, :manual) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
@@ -15,8 +16,13 @@ describe Gitlab::Ci::Status::Build::Play do
describe '#has_action?' do
context 'when user is allowed to update build' do
- context 'when user can push to branch' do
- before { build.project.add_master(user) }
+ context 'when user is allowed to trigger protected action' do
+ before do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
+ end
it { is_expected.to have_action }
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index fdbb55fc874..b1999409170 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -244,7 +244,7 @@ describe Gitlab::Workhorse, lib: true do
context "when git_receive_pack action is passed" do
let(:action) { 'git_receive_pack' }
- it { expect(subject).not_to include(gitaly_params) }
+ it { expect(subject).to include(gitaly_params) }
end
context "when info_refs action is passed" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 12519de8636..9fbe19b04d5 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -227,7 +227,10 @@ describe Environment, models: true do
context 'when user is allowed to stop environment' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
context 'when action did not yet finish' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2ef3d5654d5..da1b29a2bda 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -948,6 +948,20 @@ describe Project, models: true do
end
end
+ describe '.starred_by' do
+ it 'returns only projects starred by the given user' do
+ user1 = create(:user)
+ user2 = create(:user)
+ project1 = create(:empty_project)
+ project2 = create(:empty_project)
+ create(:empty_project)
+ user1.toggle_star(project1)
+ user2.toggle_star(project2)
+
+ expect(Project.starred_by(user1)).to contain_exactly(project1)
+ end
+ end
+
describe '.visible_to_user' do
let!(:project) { create(:empty_project, :private) }
let!(:user) { create(:user) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9edf34b05ad..fe9df3360ff 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1496,25 +1496,6 @@ describe User, models: true do
end
end
- describe '#viewable_starred_projects' do
- let(:user) { create(:user) }
- let(:public_project) { create(:empty_project, :public) }
- let(:private_project) { create(:empty_project, :private) }
- let(:private_viewable_project) { create(:empty_project, :private) }
-
- before do
- private_viewable_project.team << [user, Gitlab::Access::MASTER]
-
- [public_project, private_project, private_viewable_project].each do |project|
- user.toggle_star(project)
- end
- end
-
- it 'returns only starred projects the user can view' do
- expect(user.viewable_starred_projects).not_to include(private_project)
- end
- end
-
describe '#projects_with_reporter_access_limited_to' do
let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) }
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
new file mode 100644
index 00000000000..85d11deb26f
--- /dev/null
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -0,0 +1,297 @@
+require 'spec_helper'
+
+describe API::PipelineSchedules do
+ set(:developer) { create(:user) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules' do
+ context 'authenticated user with valid permissions' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ it 'returns list of pipeline_schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('pipeline_schedules')
+ end
+
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.count
+
+ create_list(:ci_pipeline_schedule, 10, project: project)
+ .each do |pipeline_schedule|
+ create(:user).tap do |user|
+ project.add_developer(user)
+ pipeline_schedule.update_attributes(owner: user)
+ end
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ expect do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.not_to exceed_query_limit(control_count)
+ end
+
+ %w[active inactive].each do |target|
+ context "when scope is #{target}" do
+ before do
+ create(:ci_pipeline_schedule, project: project, active: active?(target))
+ end
+
+ it 'returns matched pipeline schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
+
+ expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
+ end
+ end
+
+ def active?(str)
+ (str == 'active') ? true : false
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'returns pipeline_schedule details' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules' do
+ let(:params) { attributes_for(:ci_pipeline_schedule) }
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params
+ end.to change { project.pipeline_schedules.count }.by(1)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['ref']).to eq(params[:ref])
+ expect(json_response['cron']).to eq(params[:cron])
+ expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
+ expect(json_response['owner']['id']).to eq(developer.id)
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+
+ context 'when cron has validation error' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params.merge('cron' => 'invalid-cron')
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", user), params
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules"), params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates cron' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: '1 2 3 4 *'
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['cron']).to eq('1 2 3 4 *')
+ end
+
+ context 'when cron has validation error' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: 'invalid-cron'
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:master) { create(:user) }
+
+ let!(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ before do
+ project.add_master(master)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/-5", master)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index f95a287a184..3d98551628b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -390,6 +390,14 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'sets tag list to a project' do
+ project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
+
+ post api('/projects', user), project
+
+ expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
+ end
+
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
post api('/projects', user), project
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index b5eb84ae43b..6d5e1046e86 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe BuildEntity do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
+ let(:project) { build.project }
let(:request) { double('request') }
before do
@@ -52,7 +53,10 @@ describe BuildEntity do
context 'when user is allowed to trigger action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'contains path to play action' do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index d6f9fa42045..ea211de1f82 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -13,8 +13,11 @@ describe Ci::PlayBuildService, '#execute', :services do
context 'when project does not have repository yet' do
let(:project) { create(:empty_project) }
- it 'allows user with master role to play build' do
- project.add_master(user)
+ it 'allows user to play build if protected branch rules are met' do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
service.execute(build)
@@ -45,7 +48,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'enqueues the build' do
@@ -64,7 +70,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'duplicates the build' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index fc5de5d069a..1557cb3c938 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -333,10 +333,11 @@ describe Ci::ProcessPipelineService, '#execute', :services do
context 'when pipeline is promoted sequentially up to the end' do
before do
- # We are using create(:empty_project), and users has to be master in
- # order to execute manual action when repository does not exist.
+ # Users need ability to merge into a branch in order to trigger
+ # protected manual actions.
#
- project.add_master(user)
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'properly processes entire pipeline' do
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index d941d56c0d8..3e860203063 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -6,9 +6,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:service) { described_class.new(project, user) }
- context 'when user has ability to modify pipeline' do
+ context 'when user has full ability to modify pipeline' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
end
context 'when there are already retried jobs present' do